diff --git a/src/app/App.tsx b/src/app/App.tsx index 6a941a79..64445183 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -1,11 +1,12 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2020-2021 The Pybricks Authors +// Copyright (c) 2020-2022 The Pybricks Authors import { Classes } from '@blueprintjs/core'; import React, { useEffect, useMemo, useState } from 'react'; import { useDispatch } from 'react-redux'; import SplitterLayout from 'react-splitter-layout'; import Editor, { EditorContext, EditorContextType, EditorType } from '../editor/Editor'; +import Explorer from '../explorer/Explorer'; import { useSelector } from '../reducers'; import { toggleBoolean } from '../settings/actions'; import { BooleanSettingId } from '../settings/defaults'; @@ -176,21 +177,31 @@ const App: React.VoidFunctionComponent = (props) => { localStorage.setItem('app-docs-split', String(value)) } > - - localStorage.setItem('app-terminal-split', String(value)) - } - > - -
- +
+
+
- +
+ + localStorage.setItem( + 'app-terminal-split', + String(value), + ) + } + > + +
+ +
+
+
+
{isDragging &&
} diff --git a/src/explorer/Explorer.test.tsx b/src/explorer/Explorer.test.tsx new file mode 100644 index 00000000..c95dcbcc --- /dev/null +++ b/src/explorer/Explorer.test.tsx @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2022 The Pybricks Authors + +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { testRender } from '../../test'; +import Explorer from './Explorer'; + +describe('archive button', () => { + it('should be enabled if there are files', () => { + const explorer = testRender(, { + fileStorage: { fileNames: ['test.file'] }, + }); + + expect(explorer.getByTitle('Backup all files')).toBeEnabled(); + }); + + it('should be disabled if there are no files', () => { + const explorer = testRender(, { + fileStorage: { fileNames: [] }, + }); + + expect(explorer.getByTitle('Backup all files')).toBeDisabled(); + }); +}); + +describe('list item', () => { + it('should show/hide buttons on hover', () => { + const explorer = testRender(, { + fileStorage: { fileNames: ['test.file'] }, + }); + + const button = explorer.getByTitle('Rename test.file'); + + // by default, the buttons are hidden + expect(button).not.toBeVisible(); + + // but are visible when hovered + userEvent.hover(button); + expect(button).toBeVisible(); + + // and hide again when unhovered + userEvent.unhover(button); + expect(button).not.toBeVisible(); + }); + + it('should not focus buttons on click', () => { + const explorer = testRender(, { + fileStorage: { fileNames: ['test.file'] }, + }); + + const button = explorer.getByTitle('Rename test.file'); + + userEvent.click(button); + expect(button).not.toHaveFocus(); + }); +}); diff --git a/src/explorer/Explorer.tsx b/src/explorer/Explorer.tsx new file mode 100644 index 00000000..ca26d5a7 --- /dev/null +++ b/src/explorer/Explorer.tsx @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2022 The Pybricks Authors + +// A file explorer control. + +import { + Button, + ButtonGroup, + Divider, + IconName, + Tree, + TreeNodeInfo, +} from '@blueprintjs/core'; +import { useI18n } from '@shopify/react-i18n'; +import React, { forwardRef, useImperativeHandle, useMemo, useState } from 'react'; +import { useDispatch } from 'react-redux'; +import { + fileStorageArchiveAllFiles, + fileStorageExportFile, +} from '../fileStorage/actions'; +import { useSelector } from '../reducers'; +import { ExplorerStringId } from './i18n'; +import en from './i18n.en.json'; + +type ActionButtonProps = { + /** The icon to use for the button. */ + icon: IconName; + /** The tooltip translation ID for the tooltip text. */ + toolTipId: ExplorerStringId; + /** Replacements if required by `toolTipId` */ + toolTipReplacements?: { [key: string]: string }; + /** If provided, controls button disabled state. */ + disabled?: boolean; + /** Callback for button click event. */ + onClick: () => void; +}; + +const ActionButton: React.VoidFunctionComponent = (props) => { + const [i18n] = useI18n({ id: 'explorer', translations: { en }, fallback: en }); + + return ( +