From 7ecc7bc23bf56e62c014cc93185442ad54f4cbd7 Mon Sep 17 00:00:00 2001 From: Ian Yong Date: Fri, 24 Mar 2023 00:45:44 +0800 Subject: [PATCH 1/6] Shorten editor tab file paths --- .../editor/tabs/EditorTabContainer.tsx | 5 ++- src/commons/editor/tabs/utils.ts | 39 +++++++++++++++++++ src/pages/playground/Playground.tsx | 2 +- 3 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 src/commons/editor/tabs/utils.ts diff --git a/src/commons/editor/tabs/EditorTabContainer.tsx b/src/commons/editor/tabs/EditorTabContainer.tsx index 52bc06cd58..b708b12957 100644 --- a/src/commons/editor/tabs/EditorTabContainer.tsx +++ b/src/commons/editor/tabs/EditorTabContainer.tsx @@ -1,6 +1,7 @@ import React from 'react'; import EditorTab from './EditorTab'; +import { getShortestUniqueFilePaths } from './utils'; export type EditorTabContainerProps = { filePaths: string[]; @@ -19,9 +20,11 @@ const EditorTabContainer: React.FC = (props: EditorTabC }); }; + const shortenedFilePaths = getShortestUniqueFilePaths(filePaths); + return (
- {filePaths.map((filePath, index) => ( + {shortenedFilePaths.map((filePath, index) => ( { + const originalToTransformedFilePaths: Record = {}; + const filePathSegments: Record = originalFilePaths.reduce( + (segments, filePath) => ({ ...segments, [filePath]: filePath.split('/') }), + {} + ); + + for ( + let numOfPathSegments = 1; + Object.keys(originalToTransformedFilePaths).length < originalFilePaths.length; + numOfPathSegments++ + ) { + const transformedToOriginalFilePaths: Record = Object.entries( + filePathSegments + ).reduce((filePaths, [originalFilePath, filePathSegments]) => { + // Note that if there are fewer path segments than the number being sliced, + // all of the path segments will be returned without error. + const transformedFilePath = '/' + filePathSegments.slice(-numOfPathSegments).join('/'); + return { + ...filePaths, + [transformedFilePath]: (filePaths[transformedFilePath] ?? []).concat(originalFilePath) + }; + }, {}); + Object.entries(transformedToOriginalFilePaths).forEach( + ([transformedFilePath, originalFilePaths]) => { + if (originalFilePaths.length > 1) { + return; + } + + const originalFilePath = originalFilePaths[0]; + originalToTransformedFilePaths[originalFilePath] = transformedFilePath; + // Remove the file path's segments from the next iteration. + delete filePathSegments[originalFilePath]; + } + ); + } + + return originalFilePaths.map(filePath => originalToTransformedFilePaths[filePath]); +}; diff --git a/src/pages/playground/Playground.tsx b/src/pages/playground/Playground.tsx index b72d53e78a..a5bce3613b 100644 --- a/src/pages/playground/Playground.tsx +++ b/src/pages/playground/Playground.tsx @@ -602,7 +602,7 @@ const Playground: React.FC = ({ workspaceLocation = 'playground const toggleFolderModeButton = React.useMemo(() => { // TODO: Remove this once the Folder mode is ready for production. - if (true) { + if (false) { return <>; } From 172290434811a2f6fa5f909d679b8223676a25cc Mon Sep 17 00:00:00 2001 From: Ian Yong Date: Fri, 24 Mar 2023 00:55:15 +0800 Subject: [PATCH 2/6] Remove empty file path segments --- src/commons/editor/tabs/utils.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/commons/editor/tabs/utils.ts b/src/commons/editor/tabs/utils.ts index b5107e123b..0f14a7aa01 100644 --- a/src/commons/editor/tabs/utils.ts +++ b/src/commons/editor/tabs/utils.ts @@ -1,7 +1,10 @@ export const getShortestUniqueFilePaths = (originalFilePaths: string[]): string[] => { const originalToTransformedFilePaths: Record = {}; const filePathSegments: Record = originalFilePaths.reduce( - (segments, filePath) => ({ ...segments, [filePath]: filePath.split('/') }), + (segments, filePath) => ({ + ...segments, + [filePath]: filePath.split('/').filter(segment => segment !== '') + }), {} ); From e5878649f76129acc1e38c5f4edae832f2f42170 Mon Sep 17 00:00:00 2001 From: Ian Yong Date: Fri, 24 Mar 2023 00:55:37 +0800 Subject: [PATCH 3/6] Add test cases for getting the shortened unique file paths --- src/commons/editor/__tests__/tabs/utils.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/commons/editor/__tests__/tabs/utils.ts diff --git a/src/commons/editor/__tests__/tabs/utils.ts b/src/commons/editor/__tests__/tabs/utils.ts new file mode 100644 index 0000000000..bc69c89fb9 --- /dev/null +++ b/src/commons/editor/__tests__/tabs/utils.ts @@ -0,0 +1,15 @@ +import { getShortestUniqueFilePaths } from '../../tabs/utils'; + +describe('getShortestUniqueFilePaths', () => { + it('returns the shortest unique file paths', () => { + const filePaths = ['/dir/dir1/a.js', '/dir/dir2/a.js', '/dir/dir1/b.js']; + const shortenedFilePaths = getShortestUniqueFilePaths(filePaths); + expect(shortenedFilePaths).toEqual(['/dir1/a.js', '/dir2/a.js', '/b.js']); + }); + + it('works even when the number of path segments in a file path is less than the number of iterations', () => { + const filePaths = ['/a.js', '/dir/dir2/a.js']; + const shortenedFilePaths = getShortestUniqueFilePaths(filePaths); + expect(shortenedFilePaths).toEqual(['/a.js', '/dir2/a.js']); + }); +}); From 29e2801381899e3f03cb3347cdcf944067702243 Mon Sep 17 00:00:00 2001 From: Ian Yong Date: Fri, 24 Mar 2023 01:04:31 +0800 Subject: [PATCH 4/6] Exclude the workspace base path from being displayed in editor tab --- src/commons/editor/EditorContainer.tsx | 3 +++ src/commons/editor/tabs/EditorTabContainer.tsx | 13 ++++++++++--- src/pages/fileSystem/createInBrowserFileSystem.ts | 3 ++- src/pages/playground/Playground.tsx | 12 ++++++++++-- 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/commons/editor/EditorContainer.tsx b/src/commons/editor/EditorContainer.tsx index 8938864f19..9aa02b2765 100644 --- a/src/commons/editor/EditorContainer.tsx +++ b/src/commons/editor/EditorContainer.tsx @@ -13,6 +13,7 @@ import Editor, { EditorProps, EditorTabStateProps } from './Editor'; import EditorTabContainer from './tabs/EditorTabContainer'; type OwnProps = { + baseFilePath?: string; isFolderModeEnabled: boolean; activeEditorTabIndex: number | null; setActiveEditorTabIndex: (activeEditorTabIndex: number | null) => void; @@ -60,6 +61,7 @@ const createSourcecastEditorTab = const EditorContainer: React.FC = (props: EditorContainerProps) => { const { + baseFilePath, isFolderModeEnabled, activeEditorTabIndex, setActiveEditorTabIndex, @@ -87,6 +89,7 @@ const EditorContainer: React.FC = (props: EditorContainerP
{isFolderModeEnabled && ( void; @@ -11,8 +12,13 @@ export type EditorTabContainerProps = { }; const EditorTabContainer: React.FC = (props: EditorTabContainerProps) => { - const { filePaths, activeEditorTabIndex, setActiveEditorTabIndex, removeEditorTabByIndex } = - props; + const { + baseFilePath, + filePaths, + activeEditorTabIndex, + setActiveEditorTabIndex, + removeEditorTabByIndex + } = props; const handleHorizontalScroll = (e: React.WheelEvent) => { e.currentTarget.scrollTo({ @@ -20,7 +26,8 @@ const EditorTabContainer: React.FC = (props: EditorTabC }); }; - const shortenedFilePaths = getShortestUniqueFilePaths(filePaths); + const relativeFilePaths = filePaths.map(filePath => filePath.replace(baseFilePath, '')); + const shortenedFilePaths = getShortestUniqueFilePaths(relativeFilePaths); return (
diff --git a/src/pages/fileSystem/createInBrowserFileSystem.ts b/src/pages/fileSystem/createInBrowserFileSystem.ts index 8ad27ee8a2..7d61360c99 100644 --- a/src/pages/fileSystem/createInBrowserFileSystem.ts +++ b/src/pages/fileSystem/createInBrowserFileSystem.ts @@ -3,13 +3,14 @@ import { ApiError } from 'browserfs/dist/node/core/api_error'; import { Store } from 'redux'; import { setInBrowserFileSystem } from '../../commons/fileSystem/FileSystemActions'; +import { BASE_PLAYGROUND_FILE_PATH } from '../playground/Playground'; export const createInBrowserFileSystem = (store: Store) => { configure( { fs: 'MountableFileSystem', options: { - '/playground': { + [BASE_PLAYGROUND_FILE_PATH]: { fs: 'IndexedDB', options: { storeName: 'playground' diff --git a/src/pages/playground/Playground.tsx b/src/pages/playground/Playground.tsx index a5bce3613b..27af36168b 100644 --- a/src/pages/playground/Playground.tsx +++ b/src/pages/playground/Playground.tsx @@ -202,6 +202,8 @@ export async function handleHash(hash: string, props: PlaygroundProps) { } } +export const BASE_PLAYGROUND_FILE_PATH = '/playground'; + const Playground: React.FC = ({ workspaceLocation = 'playground', ...props }) => { const { isSicpEditor } = props; const { isMobileBreakpoint } = useResponsive(); @@ -602,7 +604,7 @@ const Playground: React.FC = ({ workspaceLocation = 'playground const toggleFolderModeButton = React.useMemo(() => { // TODO: Remove this once the Folder mode is ready for production. - if (false) { + if (true) { return <>; } @@ -809,6 +811,7 @@ const Playground: React.FC = ({ workspaceLocation = 'playground const editorContainerProps: NormalEditorContainerProps = { ..._.pick(props, 'editorSessionId', 'isEditorAutorun'), editorVariant: 'normal', + baseFilePath: BASE_PLAYGROUND_FILE_PATH, isFolderModeEnabled, activeEditorTabIndex, setActiveEditorTabIndex, @@ -880,7 +883,12 @@ const Playground: React.FC = ({ workspaceLocation = 'playground ? [ { label: 'Folder', - body: , + body: ( + + ), iconName: IconNames.FOLDER_CLOSE, id: SideContentType.folder } From 24634c71f569255cda7159895527276f516604b1 Mon Sep 17 00:00:00 2001 From: Ian Yong Date: Fri, 24 Mar 2023 13:59:54 +0800 Subject: [PATCH 5/6] Add comments & better name variables in getShortestUniqueFilePaths --- src/commons/editor/tabs/utils.ts | 54 ++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/src/commons/editor/tabs/utils.ts b/src/commons/editor/tabs/utils.ts index 0f14a7aa01..5be6aa6669 100644 --- a/src/commons/editor/tabs/utils.ts +++ b/src/commons/editor/tabs/utils.ts @@ -1,8 +1,35 @@ +/** + * Returns the shortest file paths that is uniquely identifiable among + * all open editor tabs. This is similar to how most code editors available + * handle the displaying of file names. + * + * For example, if there are 3 open editor tabs where the file names are + * exactly the same, we would need to display more of the file path to be + * able to distinguish which editor tab corresponds to which file. Given + * the following absolute file paths: + * - /a.js + * - /dir1/a.js + * - /dir1/dir2/a.js + * The shortest unique file paths will be: + * - /a.js + * - /dir1/a.js + * - /dir2/a.js + * + * @param originalFilePaths The file paths to be shortened. + */ export const getShortestUniqueFilePaths = (originalFilePaths: string[]): string[] => { - const originalToTransformedFilePaths: Record = {}; + // Store the unique shortened file paths as a mapping from the original file paths + // to the shortened file paths. This is necessary because the output of this function + // must preserve the original ordering of file paths. + const originalToUniqueShortenedFilePaths: Record = {}; + // Split each original file path into path segments and store the mapping from file + // path to path segments for O(1) lookup. Since we only deal with the BrowserFS file + // system, the path separator will always be '/'. const filePathSegments: Record = originalFilePaths.reduce( (segments, filePath) => ({ ...segments, + // It is necessary to remove empty segments to deal with the very first '/' in + // file paths. [filePath]: filePath.split('/').filter(segment => segment !== '') }), {} @@ -10,33 +37,42 @@ export const getShortestUniqueFilePaths = (originalFilePaths: string[]): string[ for ( let numOfPathSegments = 1; - Object.keys(originalToTransformedFilePaths).length < originalFilePaths.length; + // Keep looping while some original file paths have yet to be shortened. + Object.keys(originalToUniqueShortenedFilePaths).length < originalFilePaths.length; numOfPathSegments++ ) { - const transformedToOriginalFilePaths: Record = Object.entries( + // Based on the number of path segments for the iteration, we construct the + // shortened file path. We then store the mapping from the shortened file path + // to any original file path which transforms into it. + const shortenedToOriginalFilePaths: Record = Object.entries( filePathSegments ).reduce((filePaths, [originalFilePath, filePathSegments]) => { // Note that if there are fewer path segments than the number being sliced, // all of the path segments will be returned without error. - const transformedFilePath = '/' + filePathSegments.slice(-numOfPathSegments).join('/'); + const shortenedFilePath = '/' + filePathSegments.slice(-numOfPathSegments).join('/'); return { ...filePaths, - [transformedFilePath]: (filePaths[transformedFilePath] ?? []).concat(originalFilePath) + [shortenedFilePath]: (filePaths[shortenedFilePath] ?? []).concat(originalFilePath) }; }, {}); - Object.entries(transformedToOriginalFilePaths).forEach( - ([transformedFilePath, originalFilePaths]) => { + // Each shortened file path that only has a single corresponding original file + // path is added to the unique shortened file paths record and their entry in + // the file path segments record is removed to prevent further processing. + Object.entries(shortenedToOriginalFilePaths).forEach( + ([shortenedFilePath, originalFilePaths]) => { if (originalFilePaths.length > 1) { return; } const originalFilePath = originalFilePaths[0]; - originalToTransformedFilePaths[originalFilePath] = transformedFilePath; + originalToUniqueShortenedFilePaths[originalFilePath] = shortenedFilePath; // Remove the file path's segments from the next iteration. delete filePathSegments[originalFilePath]; } ); } - return originalFilePaths.map(filePath => originalToTransformedFilePaths[filePath]); + // Finally, we retrieve the unique shortened file paths while preserving the ordering + // of file paths. + return originalFilePaths.map(filePath => originalToUniqueShortenedFilePaths[filePath]); }; From 5acf3c8703c1af4c9ac3707af2437502aa2c5b2e Mon Sep 17 00:00:00 2001 From: Ian Yong Date: Fri, 24 Mar 2023 14:07:33 +0800 Subject: [PATCH 6/6] Fix value of default base file path We need to pass in an empty string instead of '/' because otherwise, the first slash of file paths would be removed and the result would not be file paths. --- src/commons/editor/EditorContainer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commons/editor/EditorContainer.tsx b/src/commons/editor/EditorContainer.tsx index 9aa02b2765..c59b8f06e0 100644 --- a/src/commons/editor/EditorContainer.tsx +++ b/src/commons/editor/EditorContainer.tsx @@ -89,7 +89,7 @@ const EditorContainer: React.FC = (props: EditorContainerP
{isFolderModeEnabled && (