From e37d980453b99cf19874248b703a423d42d6c7b4 Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Mon, 5 Sep 2022 16:21:26 +0100 Subject: [PATCH] Chore: Converted desktop Sidebar to React Hooks --- packages/app-desktop/gui/Sidebar/Sidebar.tsx | 482 +++++++++--------- .../Sidebar/commands/focusElementSideBar.ts | 14 +- 2 files changed, 238 insertions(+), 258 deletions(-) diff --git a/packages/app-desktop/gui/Sidebar/Sidebar.tsx b/packages/app-desktop/gui/Sidebar/Sidebar.tsx index 3c9df32029..3489e5e851 100644 --- a/packages/app-desktop/gui/Sidebar/Sidebar.tsx +++ b/packages/app-desktop/gui/Sidebar/Sidebar.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import { useEffect, useRef, useCallback } from 'react'; import { StyledRoot, StyledAddButton, StyledShareIcon, StyledHeader, StyledHeaderIcon, StyledAllNotesIcon, StyledHeaderLabel, StyledListItem, StyledListItemAnchor, StyledExpandLink, StyledNoteCount, StyledSyncReportText, StyledSyncReport, StyledSynchronizeButton } from './styles'; import { ButtonLevel } from '../Button/Button'; import CommandService from '@joplin/lib/services/CommandService'; @@ -23,6 +24,8 @@ import { store } from '@joplin/lib/reducer'; import PerFolderSortOrderService from '../../services/sortOrder/PerFolderSortOrderService'; import { getFolderCallbackUrl, getTagCallbackUrl } from '@joplin/lib/callbackUrlUtils'; import FolderIconBox from '../FolderIconBox'; +import { Theme } from '@joplin/lib/themes/type'; +import { RuntimeProps } from './commands/focusElementSideBar'; const { connect } = require('react-redux'); const shared = require('@joplin/lib/components/shared/side-menu-shared.js'); const { themeStyle } = require('@joplin/lib/theme'); @@ -50,11 +53,8 @@ interface Props { tags: any[]; syncStarted: boolean; plugins: PluginStates; -} - -interface State { - tagHeaderIsExpanded: boolean; folderHeaderIsExpanded: boolean; + tagHeaderIsExpanded: boolean; } const commands = [ @@ -119,56 +119,80 @@ function FolderItem(props: any) { const menuUtils = new MenuUtils(CommandService.instance()); -class SidebarComponent extends React.Component { +const SidebarComponent = (props: Props) => { - private folderItemsOrder_: any[] = []; - private tagItemsOrder_: any[] = []; - private rootRef: any = null; - private anchorItemRefs: any = {}; - private pluginsRef: any; + const folderItemsOrder_ = useRef(); + folderItemsOrder_.current = []; + const tagItemsOrder_ = useRef(); + tagItemsOrder_.current = []; - constructor(props: any) { - super(props); + const rootRef = useRef(null); + const anchorItemRefs = useRef>(null); + anchorItemRefs.current = {}; - CommandService.instance().componentRegisterCommands(this, commands); + // This whole component is a bit of a mess and rather than passing + // a plugins prop around, not knowing how it's going to affect + // re-rendering, we just keep a ref to it. Currently that's enough + // as plugins are only accessed from context menus. However if want + // to do more complex things with plugins in the sidebar, it will + // probably have to be refactored using React Hooks first. + const pluginsRef = useRef(null); + pluginsRef.current = props.plugins; - this.state = { - tagHeaderIsExpanded: Setting.value('tagHeaderIsExpanded'), - folderHeaderIsExpanded: Setting.value('folderHeaderIsExpanded'), + const getSelectedItem = useCallback(() => { + if (props.notesParentType === 'Folder' && props.selectedFolderId) { + return { type: 'folder', id: props.selectedFolderId }; + } else if (props.notesParentType === 'Tag' && props.selectedTagId) { + return { type: 'tag', id: props.selectedTagId }; + } + + return null; + }, [props.notesParentType, props.selectedFolderId, props.selectedTagId]); + + const getFirstAnchorItemRef = useCallback((type: string) => { + const refs = anchorItemRefs.current[type]; + if (!refs) return null; + + const p = type === 'folder' ? props.folders : props.tags; + const item = p && p.length ? p[0] : null; + if (!item) return null; + + return refs[item.id]; + }, [anchorItemRefs, props.folders, props.tags]); + + useEffect(() => { + const runtimeProps: RuntimeProps = { + getSelectedItem, + anchorItemRefs, + getFirstAnchorItemRef, }; - // This whole component is a bit of a mess and rather than passing - // a plugins prop around, not knowing how it's going to affect - // re-rendering, we just keep a ref to it. Currently that's enough - // as plugins are only accessed from context menus. However if want - // to do more complex things with plugins in the sidebar, it will - // probably have to be refactored using React Hooks first. - this.pluginsRef = React.createRef(); + CommandService.instance().componentRegisterCommands(runtimeProps, commands); - this.onFolderToggleClick_ = this.onFolderToggleClick_.bind(this); - this.onKeyDown = this.onKeyDown.bind(this); - this.onAllNotesClick_ = this.onAllNotesClick_.bind(this); - this.header_contextMenu = this.header_contextMenu.bind(this); - this.onAddFolderButtonClick = this.onAddFolderButtonClick.bind(this); - this.folderItem_click = this.folderItem_click.bind(this); - this.itemContextMenu = this.itemContextMenu.bind(this); - } + return () => { + CommandService.instance().componentUnregisterCommands(commands); + }; + }, [ + getSelectedItem, + anchorItemRefs, + getFirstAnchorItemRef, + ]); - onFolderDragStart_(event: any) { + const onFolderDragStart_ = useCallback((event: any) => { const folderId = event.currentTarget.getAttribute('data-folder-id'); if (!folderId) return; event.dataTransfer.setDragImage(new Image(), 1, 1); event.dataTransfer.clearData(); event.dataTransfer.setData('text/x-jop-folder-ids', JSON.stringify([folderId])); - } + }, []); - onFolderDragOver_(event: any) { + const onFolderDragOver_ = useCallback((event: any) => { if (event.dataTransfer.types.indexOf('text/x-jop-note-ids') >= 0) event.preventDefault(); if (event.dataTransfer.types.indexOf('text/x-jop-folder-ids') >= 0) event.preventDefault(); - } + }, []); - async onFolderDrop_(event: any) { + const onFolderDrop_ = useCallback(async (event: any) => { const folderId = event.currentTarget.getAttribute('data-folder-id'); const dt = event.dataTransfer; if (!dt) return; @@ -199,9 +223,9 @@ class SidebarComponent extends React.Component { logger.error(error); alert(error.message); } - } + }, []); - async onTagDrop_(event: any) { + const onTagDrop_ = useCallback(async (event: any) => { const tagId = event.currentTarget.getAttribute('data-tag-id'); const dt = event.dataTransfer; if (!dt) return; @@ -214,22 +238,18 @@ class SidebarComponent extends React.Component { await Tag.addNote(tagId, noteIds[i]); } } - } + }, []); - async onFolderToggleClick_(event: any) { + const onFolderToggleClick_ = useCallback((event: any) => { const folderId = event.currentTarget.getAttribute('data-folder-id'); - this.props.dispatch({ + props.dispatch({ type: 'FOLDER_TOGGLE', id: folderId, }); - } + }, [props.dispatch]); - componentWillUnmount() { - CommandService.instance().componentUnregisterCommands(commands); - } - - async header_contextMenu() { + const header_contextMenu = useCallback(async () => { const menu = new Menu(); menu.append( @@ -237,9 +257,9 @@ class SidebarComponent extends React.Component { ); menu.popup(bridge().window()); - } + }, []); - async itemContextMenu(event: any) { + const itemContextMenu = useCallback(async (event: any) => { const itemId = event.currentTarget.getAttribute('data-id'); if (itemId === Folder.conflictFolderId()) return; @@ -265,7 +285,7 @@ class SidebarComponent extends React.Component { let item = null; if (itemType === BaseModel.TYPE_FOLDER) { - item = BaseModel.byId(this.props.folders, itemId); + item = BaseModel.byId(props.folders, itemId); } if (itemType === BaseModel.TYPE_FOLDER && !item.encryption_applied) { @@ -289,7 +309,7 @@ class SidebarComponent extends React.Component { } else if (itemType === BaseModel.TYPE_TAG) { await Tag.untagAll(itemId); } else if (itemType === BaseModel.TYPE_SEARCH) { - this.props.dispatch({ + props.dispatch({ type: 'SEARCH_DELETE', id: itemId, }); @@ -314,7 +334,7 @@ class SidebarComponent extends React.Component { new MenuItem({ label: module.fullLabel(), click: async () => { - await InteropServiceHelper.export(this.props.dispatch.bind(this), module, { sourceFolderIds: [itemId], plugins: this.pluginsRef.current }); + await InteropServiceHelper.export(props.dispatch, module, { sourceFolderIds: [itemId], plugins: pluginsRef.current }); }, }) ); @@ -374,7 +394,7 @@ class SidebarComponent extends React.Component { ); } - const pluginViews = pluginUtils.viewsByType(this.pluginsRef.current, 'menuItem'); + const pluginViews = pluginUtils.viewsByType(pluginsRef.current, 'menuItem'); for (const view of pluginViews) { const location = view.location; @@ -389,80 +409,79 @@ class SidebarComponent extends React.Component { } menu.popup(bridge().window()); - } + }, [props.folders, props.dispatch, pluginsRef]); - folderItem_click(folderId: string) { - this.props.dispatch({ + const folderItem_click = useCallback((folderId: string) => { + props.dispatch({ type: 'FOLDER_SELECT', id: folderId ? folderId : null, }); - } + }, [props.dispatch]); - tagItem_click(tag: any) { - this.props.dispatch({ + const tagItem_click = useCallback((tag: any) => { + props.dispatch({ type: 'TAG_SELECT', id: tag ? tag.id : null, }); - } + }, [props.dispatch]); - anchorItemRef(type: string, id: string) { - if (!this.anchorItemRefs[type]) this.anchorItemRefs[type] = {}; - if (this.anchorItemRefs[type][id]) return this.anchorItemRefs[type][id]; - this.anchorItemRefs[type][id] = React.createRef(); - return this.anchorItemRefs[type][id]; - } + const onHeaderClick_ = useCallback((key: string) => { + const isExpanded = key === 'tag' ? props.tagHeaderIsExpanded : props.folderHeaderIsExpanded; + Setting.setValue(key === 'tag' ? 'tagHeaderIsExpanded' : 'folderHeaderIsExpanded', !isExpanded); + }, [props.folderHeaderIsExpanded, props.tagHeaderIsExpanded]); - firstAnchorItemRef(type: string) { - const refs = this.anchorItemRefs[type]; - if (!refs) return null; + const onAllNotesClick_ = () => { + props.dispatch({ + type: 'SMART_FILTER_SELECT', + id: ALL_NOTES_FILTER_ID, + }); + }; - const n = `${type}s`; - const p = this.props as any; - const item = p[n] && p[n].length ? p[n][0] : null; - if (!item) return null; + const anchorItemRef = (type: string, id: string) => { + if (!anchorItemRefs.current[type]) anchorItemRefs.current[type] = {}; + if (anchorItemRefs.current[type][id]) return anchorItemRefs.current[type][id]; + anchorItemRefs.current[type][id] = React.createRef(); + return anchorItemRefs.current[type][id]; + }; - return refs[item.id]; - } - - renderNoteCount(count: number) { + const renderNoteCount = (count: number) => { return count ? {count} : null; - } + }; - renderExpandIcon(isExpanded: boolean, isVisible: boolean = true) { - const theme = themeStyle(this.props.themeId); + const renderExpandIcon = (theme: any, isExpanded: boolean, isVisible: boolean) => { const style: any = { width: 16, maxWidth: 16, opacity: 0.5, fontSize: Math.round(theme.toolbarIconSize * 0.8), display: 'flex', justifyContent: 'center' }; if (!isVisible) style.visibility = 'hidden'; return ; - } + }; - renderAllNotesItem(selected: boolean) { + const renderAllNotesItem = (theme: Theme, selected: boolean) => { return ( - {this.renderExpandIcon(false, false)} + {renderExpandIcon(theme, false, false)} {_('All notes')} ); - } + }; - renderFolderItem(folder: FolderEntity, selected: boolean, hasChildren: boolean, depth: number) { - const anchorRef = this.anchorItemRef('folder', folder.id); - const isExpanded = this.props.collapsedFolderIds.indexOf(folder.id) < 0; + const renderFolderItem = (folder: FolderEntity, selected: boolean, hasChildren: boolean, depth: number) =>{ + const anchorRef = anchorItemRef('folder', folder.id); + const isExpanded = props.collapsedFolderIds.indexOf(folder.id) < 0; let noteCount = (folder as any).note_count; // Thunderbird count: Subtract children note_count from parent folder if it expanded. if (isExpanded) { - for (let i = 0; i < this.props.folders.length; i++) { - if (this.props.folders[i].parent_id === folder.id) { - noteCount -= this.props.folders[i].note_count; + for (let i = 0; i < props.folders.length; i++) { + if (props.folders[i].parent_id === folder.id) { + noteCount -= props.folders[i].note_count; } } } @@ -472,40 +491,40 @@ class SidebarComponent extends React.Component { folderId={folder.id} folderTitle={Folder.displayTitle(folder)} folderIcon={Folder.unserializeIcon(folder.icon)} - themeId={this.props.themeId} + themeId={props.themeId} depth={depth} selected={selected} isExpanded={isExpanded} hasChildren={hasChildren} anchorRef={anchorRef} noteCount={noteCount} - onFolderDragStart_={this.onFolderDragStart_} - onFolderDragOver_={this.onFolderDragOver_} - onFolderDrop_={this.onFolderDrop_} - itemContextMenu={this.itemContextMenu} - folderItem_click={this.folderItem_click} - onFolderToggleClick_={this.onFolderToggleClick_} + onFolderDragStart_={onFolderDragStart_} + onFolderDragOver_={onFolderDragOver_} + onFolderDrop_={onFolderDrop_} + itemContextMenu={itemContextMenu} + folderItem_click={folderItem_click} + onFolderToggleClick_={onFolderToggleClick_} shareId={folder.share_id} parentId={folder.parent_id} />; - } + }; - renderTag(tag: any, selected: boolean) { - const anchorRef = this.anchorItemRef('tag', tag.id); + const renderTag = (tag: any, selected: boolean) => { + const anchorRef = anchorItemRef('tag', tag.id); let noteCount = null; if (Setting.value('showNoteCounts')) { - if (Setting.value('showCompletedTodos')) noteCount = this.renderNoteCount(tag.note_count); - else noteCount = this.renderNoteCount(tag.note_count - tag.todo_completed_count); + if (Setting.value('showCompletedTodos')) noteCount = renderNoteCount(tag.note_count); + else noteCount = renderNoteCount(tag.note_count - tag.todo_completed_count); } return ( - {this.renderExpandIcon(false, false)} + {renderExpandIcon(theme, false, false)} { selected={selected} data-id={tag.id} data-type={BaseModel.TYPE_TAG} - onContextMenu={this.itemContextMenu} + onContextMenu={itemContextMenu} onClick={() => { - this.tagItem_click(tag); + tagItem_click(tag); }} > {Tag.displayTitle(tag)} @@ -523,16 +542,12 @@ class SidebarComponent extends React.Component { ); - } + }; - makeDivider(key: string) { - return
; - } - - renderHeader(key: string, label: string, iconName: string, contextMenuHandler: Function = null, onPlusButtonClick: Function = null, extraProps: any = {}) { + const renderHeader = (key: string, label: string, iconName: string, contextMenuHandler: Function = null, onPlusButtonClick: Function = null, extraProps: any = {}) => { const headerClick = extraProps.onClick || null; delete extraProps.onClick; - const ref = this.anchorItemRef('headers', key); + const ref = anchorItemRef('headers', key); return (
@@ -545,7 +560,7 @@ class SidebarComponent extends React.Component { if (headerClick) { headerClick(key, event); } - this.onHeaderClick_(key); + onHeaderClick_(key); }} > @@ -554,21 +569,11 @@ class SidebarComponent extends React.Component { { onPlusButtonClick && }
); - } + }; - selectedItem() { - if (this.props.notesParentType === 'Folder' && this.props.selectedFolderId) { - return { type: 'folder', id: this.props.selectedFolderId }; - } else if (this.props.notesParentType === 'Tag' && this.props.selectedTagId) { - return { type: 'tag', id: this.props.selectedTagId }; - } - - return null; - } - - onKeyDown(event: any) { + const onKeyDown = useCallback((event: any) => { const keyCode = event.keyCode; - const selectedItem = this.selectedItem(); + const selectedItem = getSelectedItem(); if (keyCode === 40 || keyCode === 38) { // DOWN / UP @@ -576,14 +581,14 @@ class SidebarComponent extends React.Component { const focusItems = []; - for (let i = 0; i < this.folderItemsOrder_.length; i++) { - const id = this.folderItemsOrder_[i]; - focusItems.push({ id: id, ref: this.anchorItemRefs['folder'][id], type: 'folder' }); + for (let i = 0; i < folderItemsOrder_.current.length; i++) { + const id = folderItemsOrder_.current[i]; + focusItems.push({ id: id, ref: anchorItemRefs.current['folder'][id], type: 'folder' }); } - for (let i = 0; i < this.tagItemsOrder_.length; i++) { - const id = this.tagItemsOrder_[i]; - focusItems.push({ id: id, ref: this.anchorItemRefs['tag'][id], type: 'tag' }); + for (let i = 0; i < tagItemsOrder_.current.length; i++) { + const id = tagItemsOrder_.current[i]; + focusItems.push({ id: id, ref: anchorItemRefs.current['tag'][id], type: 'tag' }); } let currentIndex = 0; @@ -604,7 +609,7 @@ class SidebarComponent extends React.Component { const actionName = `${focusItem.type.toUpperCase()}_SELECT`; - this.props.dispatch({ + props.dispatch({ type: actionName, id: focusItem.id, }); @@ -627,7 +632,7 @@ class SidebarComponent extends React.Component { // SPACE event.preventDefault(); - this.props.dispatch({ + props.dispatch({ type: 'FOLDER_TOGGLE', id: selectedItem.id, }); @@ -637,24 +642,9 @@ class SidebarComponent extends React.Component { // Ctrl+A key event.preventDefault(); } - } + }, [getSelectedItem, props.dispatch]); - onHeaderClick_(key: string) { - const toggleKey = `${key}IsExpanded`; - const isExpanded = (this.state as any)[toggleKey]; - const newState: any = { [toggleKey]: !isExpanded }; - this.setState(newState); - Setting.setValue(toggleKey, !isExpanded); - } - - onAllNotesClick_() { - this.props.dispatch({ - type: 'SMART_FILTER_SELECT', - id: ALL_NOTES_FILTER_ID, - }); - } - - renderSynchronizeButton(type: string) { + const renderSynchronizeButton = (type: string) => { const label = type === 'sync' ? _('Synchronise') : _('Cancel'); const iconAnimation = type !== 'sync' ? 'icon-infinite-rotation 1s linear infinite' : ''; @@ -670,116 +660,98 @@ class SidebarComponent extends React.Component { }} /> ); - } + }; - onAddFolderButtonClick() { + const onAddFolderButtonClick = useCallback(() => { void CommandService.instance().execute('newFolder'); + }, []); + + const theme = themeStyle(props.themeId); + + const items = []; + + items.push( + renderHeader('folderHeader', _('Notebooks'), 'icon-notebooks', header_contextMenu, onAddFolderButtonClick, { + onDrop: onFolderDrop_, + ['data-folder-id']: '', + toggleblock: 1, + }) + ); + + if (props.folders.length) { + const allNotesSelected = props.notesParentType === 'SmartFilter' && props.selectedSmartFilterId === ALL_NOTES_FILTER_ID; + const result = shared.renderFolders(props, renderFolderItem); + const folderItems = [renderAllNotesItem(theme, allNotesSelected)].concat(result.items); + folderItemsOrder_.current = result.order; + items.push( +
+ {folderItems} +
+ ); } - // componentDidUpdate(prevProps:any, prevState:any) { - // for (const n in prevProps) { - // if (prevProps[n] !== (this.props as any)[n]) { - // console.info('CHANGED PROPS', n); - // } - // } + items.push( + renderHeader('tagHeader', _('Tags'), 'icon-tags', null, null, { + toggleblock: 1, + }) + ); - // for (const n in prevState) { - // if (prevState[n] !== (this.state as any)[n]) { - // console.info('CHANGED STATE', n); - // } - // } - // } - - render() { - this.pluginsRef.current = this.props.plugins; - - const theme = themeStyle(this.props.themeId); - - const items = []; + if (props.tags.length) { + const result = shared.renderTags(props, renderTag); + const tagItems = result.items; + tagItemsOrder_.current = result.order; items.push( - this.renderHeader('folderHeader', _('Notebooks'), 'icon-notebooks', this.header_contextMenu, this.onAddFolderButtonClick, { - onDrop: this.onFolderDrop_, - ['data-folder-id']: '', - toggleblock: 1, - }) - ); - - if (this.props.folders.length) { - const allNotesSelected = this.props.notesParentType === 'SmartFilter' && this.props.selectedSmartFilterId === ALL_NOTES_FILTER_ID; - const result = shared.renderFolders(this.props, this.renderFolderItem.bind(this)); - const folderItems = [this.renderAllNotesItem(allNotesSelected)].concat(result.items); - this.folderItemsOrder_ = result.order; - items.push( -
- {folderItems} -
- ); - } - - items.push( - this.renderHeader('tagHeader', _('Tags'), 'icon-tags', null, null, { - toggleblock: 1, - }) - ); - - if (this.props.tags.length) { - const result = shared.renderTags(this.props, this.renderTag.bind(this)); - const tagItems = result.items; - this.tagItemsOrder_ = result.order; - - items.push( -
- {tagItems} -
- ); - } - - let decryptionReportText = ''; - if (this.props.decryptionWorker && this.props.decryptionWorker.state !== 'idle' && this.props.decryptionWorker.itemCount) { - decryptionReportText = _('Decrypting items: %d/%d', this.props.decryptionWorker.itemIndex + 1, this.props.decryptionWorker.itemCount); - } - - let resourceFetcherText = ''; - if (this.props.resourceFetcher && this.props.resourceFetcher.toFetchCount) { - resourceFetcherText = _('Fetching resources: %d/%d', this.props.resourceFetcher.fetchingCount, this.props.resourceFetcher.toFetchCount); - } - - const lines = Synchronizer.reportToLines(this.props.syncReport); - if (resourceFetcherText) lines.push(resourceFetcherText); - if (decryptionReportText) lines.push(decryptionReportText); - const syncReportText = []; - for (let i = 0; i < lines.length; i++) { - syncReportText.push( - - {lines[i]} - - ); - } - - const syncButton = this.renderSynchronizeButton(this.props.syncStarted ? 'cancel' : 'sync'); - - const syncReportComp = !syncReportText.length ? null : ( - - {syncReportText} - - ); - - return ( - -
{items}
-
- {syncReportComp} - {syncButton} -
-
+
+ {tagItems} +
); } -} + + let decryptionReportText = ''; + if (props.decryptionWorker && props.decryptionWorker.state !== 'idle' && props.decryptionWorker.itemCount) { + decryptionReportText = _('Decrypting items: %d/%d', props.decryptionWorker.itemIndex + 1, props.decryptionWorker.itemCount); + } + + let resourceFetcherText = ''; + if (props.resourceFetcher && props.resourceFetcher.toFetchCount) { + resourceFetcherText = _('Fetching resources: %d/%d', props.resourceFetcher.fetchingCount, props.resourceFetcher.toFetchCount); + } + + const lines = Synchronizer.reportToLines(props.syncReport); + if (resourceFetcherText) lines.push(resourceFetcherText); + if (decryptionReportText) lines.push(decryptionReportText); + const syncReportText = []; + for (let i = 0; i < lines.length; i++) { + syncReportText.push( + + {lines[i]} + + ); + } + + const syncButton = renderSynchronizeButton(props.syncStarted ? 'cancel' : 'sync'); + + const syncReportComp = !syncReportText.length ? null : ( + + {syncReportText} + + ); + + return ( + +
{items}
+
+ {syncReportComp} + {syncButton} +
+
+ ); +}; const mapStateToProps = (state: AppState) => { return { @@ -799,6 +771,8 @@ const mapStateToProps = (state: AppState) => { decryptionWorker: state.decryptionWorker, resourceFetcher: state.resourceFetcher, plugins: state.pluginService.plugins, + tagHeaderIsExpanded: state.settings.tagHeaderIsExpanded, + folderHeaderIsExpanded: state.settings.folderHeaderIsExpanded, }; }; diff --git a/packages/app-desktop/gui/Sidebar/commands/focusElementSideBar.ts b/packages/app-desktop/gui/Sidebar/commands/focusElementSideBar.ts index c9cf3b078c..624a240c53 100644 --- a/packages/app-desktop/gui/Sidebar/commands/focusElementSideBar.ts +++ b/packages/app-desktop/gui/Sidebar/commands/focusElementSideBar.ts @@ -9,18 +9,24 @@ export const declaration: CommandDeclaration = { parentLabel: () => _('Focus'), }; -export const runtime = (comp: any): CommandRuntime => { +export interface RuntimeProps { + getSelectedItem(): any; + getFirstAnchorItemRef(type: string): any; + anchorItemRefs: any; +} + +export const runtime = (props: RuntimeProps): CommandRuntime => { return { execute: async (context: CommandContext) => { const sidebarVisible = layoutItemProp((context.state as AppState).mainLayout, 'sideBar', 'visible'); if (sidebarVisible) { - const item = comp.selectedItem(); + const item = props.getSelectedItem(); if (item) { - const anchorRef = comp.anchorItemRefs[item.type][item.id]; + const anchorRef = props.anchorItemRefs.current[item.type][item.id]; if (anchorRef) anchorRef.current.focus(); } else { - const anchorRef = comp.firstAnchorItemRef('folder'); + const anchorRef = props.getFirstAnchorItemRef('folder'); if (anchorRef) anchorRef.current.focus(); } }