From ce33af82f7cd705a65dc5238dd17f8a3d30a4178 Mon Sep 17 00:00:00 2001 From: somebody1234 Date: Wed, 19 Jul 2023 19:48:39 +1000 Subject: [PATCH] Split `dashboard.tsx` into smaller components (#6546) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Consistent order for statements in `dashboard.tsx`; change functions back to lambdas * Create convenience aliases for each asset type * Remove obsolete FIXME * Refactor out column renderers into components * Enable `prefer-const` lint * Remove hardcoded product name * Add fixme * Enable `react-hooks` lints (not working for some reason) * Consistent messages for naming-convention lint overrides * Enable `react` lints * Extract out tables for each asset type * Refactor out column display mode switcher to a file * Switch VM check state to use an enum * Fix lint errors * Minor section change * Fix position of create forms * Fix bugs; improve debugging QoL * Add documentation for new components * Refactor out drive bar * Refactor out event handlers to variables * Minor clarifications * Refactor out directory view; some fixes; improve React DX There are still many issues when switching backends * Add `assert` * Use `backend.platform` instead of checking for properties * Fix errors when switching backend * Minor style changes; fix lint errors * Fix assert behavior * Change `Rows` to `Table` * Fixes * Fix lint errors * Fix "show dashboard" button * Implement click to rename * Fix lint errors * Fix lint errors (along with a bug in `devServiceWorker`) * Enable dev-mode on `ide watch` * Fix bug in `useAsyncEffect` introduced during merge * More fixes; new debug hooks; fix infinite loop in `auth.tsx` * Inline Cognito methods * Remove redundant `Promise.resolve`s * Fix column display * Fixes * Simplify modal type * Fix bug when opening IDE * Shift+click to select a range of table items * Implement delete multiple * Fixes * Tick and cross for rename input; fixes * Implement rename and delete directory and multi-delete directory; fixes * Optimize modal re-rendering * Make some internal `Props` private * Remove old asset selection code * Eliminate re-renders when clicking document body * Fix name flickering when renaming * Use static placeholders * Avoid refreshing entire directory on rename * Use asset name instead of ID in error messages * QoL improvements * Enable react lints and `strict-boolean-expressions` * Extract dashboard feature flags to its own module * Feature flag to show more toasts; minimize calls to `listDirectory` * Deselect selection on delete; hide unused features; add exception to PascalCase lint * Fix projects disappearing after being created * Fix name of `projectEvent` module imports * Re-disable delete when project is being closed * Fix assets refreshing when adding new projects * Refactor row state into `Table`; fix delete not being disabled again * Address review * Implement shortcut registry * Fix stop icon spinning when switching backends (ported from #6919) * Give columns names * Immediately show project as opening * Replace `asNewtype` with constructor functions * Address review * Minor bugfixes * Prepare for optimistically updated tables * wip 2 * Fix type errors * Remove indirect usages of `doRefresh` Updating the lists of items will need to be re-added later * Remove `toastPromise` * Fix `New_Directory_-Infinity` bug * wip * WIP: Begin restoring functionality to rows * Fix most issues with DirectoriesTable * Port optimistic UI from `DirectoriesTable` to all other asset tables * Fix bugs in item list events for asset tables * Merge `projectActionButton` into `projectsTable` * Remove `RenameModal`; minor context menu bugfixes * Fix bugs * Remove small default user icon * Fix more bugs * Fix bugs * Fix type error * Address review and QA * Fix optimistic UI for "manage permissions" modal * Fix "share with" modal * Fix template spinner disappearing * Allow multiple projects to be opened on local backend; fix version lifecycle returned by local backend * Fix minor bug when closing local project --------- Co-authored-by: Paweł Buchowski --- app/ide-desktop/eslint.config.js | 17 +- app/ide-desktop/lib/assets/cross.svg | 6 + app/ide-desktop/lib/assets/tick.svg | 5 + app/ide-desktop/lib/client/src/preload.ts | 13 +- .../lib/client/src/project-management.ts | 6 +- app/ide-desktop/lib/client/watch.ts | 2 +- app/ide-desktop/lib/common/src/detect.ts | 26 + app/ide-desktop/lib/content/src/index.html | 10 +- app/ide-desktop/lib/content/src/index.ts | 4 +- .../src/authentication/cognito.ts | 227 +-- .../components/confirmRegistration.tsx | 4 +- .../src/authentication/config.ts | 43 +- .../src/authentication/providers/auth.tsx | 49 +- .../src/authentication/providers/session.tsx | 16 +- .../src/authentication/service.tsx | 35 +- .../src/authentication/src/components/app.tsx | 28 +- .../src/authentication/src/config.ts | 39 +- .../authentication/src/dashboard/backend.ts | 158 +- .../authentication/src/dashboard/column.tsx | 344 ++++ .../src/dashboard/components/autocomplete.tsx | 1 + .../src/dashboard/components/chat.tsx | 9 +- .../components/columnDisplayModeSwitcher.tsx | 42 + .../components/confirmDeleteModal.tsx | 44 +- .../src/dashboard/components/contextMenu.tsx | 8 +- .../dashboard/components/contextMenuEntry.tsx | 1 - .../src/dashboard/components/createForm.tsx | 6 +- .../src/dashboard/components/dashboard.tsx | 1690 ++--------------- .../dashboard/components/directoriesTable.tsx | 578 ++++++ .../dashboard/components/directoryView.tsx | 375 ++++ .../src/dashboard/components/driveBar.tsx | 144 ++ .../src/dashboard/components/editableSpan.tsx | 91 + .../dashboard/components/fileCreateForm.tsx | 101 - .../src/dashboard/components/filesTable.tsx | 580 ++++++ .../src/dashboard/components/ide.tsx | 36 +- .../src/dashboard/components/input.tsx | 9 +- .../components/managePermissionsModal.tsx | 75 +- .../components/permissionSelector.tsx | 18 +- .../components/projectActionButton.tsx | 424 ----- .../dashboard/components/projectsTable.tsx | 1072 +++++++++++ .../src/dashboard/components/renameModal.tsx | 106 -- .../src/dashboard/components/rows.tsx | 109 -- .../dashboard/components/secretCreateForm.tsx | 94 - .../src/dashboard/components/secretsTable.tsx | 606 ++++++ .../src/dashboard/components/table.tsx | 246 +++ .../src/dashboard/components/tableColumn.tsx | 23 + .../src/dashboard/components/tableRow.tsx | 165 ++ .../src/dashboard/components/templates.tsx | 36 +- .../src/dashboard/components/theModal.tsx | 17 + .../src/dashboard/components/topBar.tsx | 28 +- .../src/dashboard/components/userMenu.tsx | 4 +- .../authentication/src/dashboard/dateTime.ts | 6 +- .../src/authentication/src/dashboard/event.ts | 17 + .../src/dashboard/events/directoryEvent.ts | 40 + .../dashboard/events/directoryListEvent.ts | 36 + .../src/dashboard/events/fileEvent.ts | 39 + .../src/dashboard/events/fileListEvent.ts | 38 + .../src/dashboard/events/projectEvent.ts | 67 + .../src/dashboard/events/projectListEvent.ts | 40 + .../src/dashboard/events/secretEvent.ts | 40 + .../src/dashboard/events/secretListEvent.ts | 39 + .../src/dashboard/featureFlags.ts | 12 + .../src/dashboard/localBackend.ts | 126 +- .../src/dashboard/permissions.ts | 23 + .../authentication/src/dashboard/presence.ts | 29 + .../src/dashboard/projectManager.ts | 11 + .../src/dashboard/remoteBackend.ts | 202 +- .../authentication/src/dashboard/shortcuts.ts | 152 ++ .../src/authentication/src/dashboard/tab.ts | 12 + .../src/dashboard/validation.ts | 9 + .../dashboard/src/authentication/src/error.ts | 25 +- .../src/authentication/src/hooks.tsx | 61 +- .../src/authentication/src/index.tsx | 12 +- .../src/authentication/src/newtype.ts | 8 +- .../authentication/src/providers/backend.tsx | 10 +- .../authentication/src/providers/logger.tsx | 2 +- .../authentication/src/providers/modal.tsx | 41 +- .../src/authentication/src/string.ts | 12 + .../src/authentication/src/uniqueString.ts | 14 + .../authentication/src/uploadMultipleFiles.ts | 62 - app/ide-desktop/lib/dashboard/src/index.tsx | 4 +- .../lib/dashboard/src/tailwind.css | 5 + app/ide-desktop/package-lock.json | 374 +++- app/ide-desktop/package.json | 4 +- 83 files changed, 6262 insertions(+), 3080 deletions(-) create mode 100644 app/ide-desktop/lib/assets/cross.svg create mode 100644 app/ide-desktop/lib/assets/tick.svg create mode 100644 app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/column.tsx create mode 100644 app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/columnDisplayModeSwitcher.tsx create mode 100644 app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/directoriesTable.tsx create mode 100644 app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/directoryView.tsx create mode 100644 app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/driveBar.tsx create mode 100644 app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/editableSpan.tsx delete mode 100644 app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/fileCreateForm.tsx create mode 100644 app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/filesTable.tsx delete mode 100644 app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/projectActionButton.tsx create mode 100644 app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/projectsTable.tsx delete mode 100644 app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/renameModal.tsx delete mode 100644 app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/rows.tsx delete mode 100644 app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/secretCreateForm.tsx create mode 100644 app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/secretsTable.tsx create mode 100644 app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/table.tsx create mode 100644 app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/tableColumn.tsx create mode 100644 app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/tableRow.tsx create mode 100644 app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/theModal.tsx create mode 100644 app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/event.ts create mode 100644 app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/events/directoryEvent.ts create mode 100644 app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/events/directoryListEvent.ts create mode 100644 app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/events/fileEvent.ts create mode 100644 app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/events/fileListEvent.ts create mode 100644 app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/events/projectEvent.ts create mode 100644 app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/events/projectListEvent.ts create mode 100644 app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/events/secretEvent.ts create mode 100644 app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/events/secretListEvent.ts create mode 100644 app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/featureFlags.ts create mode 100644 app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/permissions.ts create mode 100644 app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/presence.ts create mode 100644 app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/shortcuts.ts create mode 100644 app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/tab.ts create mode 100644 app/ide-desktop/lib/dashboard/src/authentication/src/string.ts create mode 100644 app/ide-desktop/lib/dashboard/src/authentication/src/uniqueString.ts delete mode 100644 app/ide-desktop/lib/dashboard/src/authentication/src/uploadMultipleFiles.ts diff --git a/app/ide-desktop/eslint.config.js b/app/ide-desktop/eslint.config.js index 12108e0f8cc..acab642bbda 100644 --- a/app/ide-desktop/eslint.config.js +++ b/app/ide-desktop/eslint.config.js @@ -38,7 +38,7 @@ const RELATIVE_MODULES = 'bin\\u002Fproject-manager|bin\\u002Fserver|config\\u002Fparser|authentication|config|debug|file-associations|index|ipc|log|naming|paths|preload|security|url-associations' const STRING_LITERAL = ':matches(Literal[raw=/^["\']/], TemplateLiteral)' const JSX = ':matches(JSXElement, JSXFragment)' -const NOT_PASCAL_CASE = '/^(?!_?([A-Z][a-z0-9]*)+$)/' +const NOT_PASCAL_CASE = '/^(?!do[A-Z])(?!_?([A-Z][a-z0-9]*)+$)/' const NOT_CAMEL_CASE = '/^(?!_?[a-z][a-z0-9*]*([A-Z0-9][a-z0-9]*)*$)(?!React$)/' const WHITELISTED_CONSTANTS = 'logger|.+Context|interpolationFunction.+' const NOT_CONSTANT_CASE = `/^(?!${WHITELISTED_CONSTANTS}$|_?[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$)/` @@ -103,7 +103,8 @@ const RESTRICTED_SYNTAXES = [ message: 'Use `for (const x of xs)`, not `for (let x of xs)`', }, { - selector: 'TSTypeAliasDeclaration > TSTypeReference > Identifier', + selector: + 'TSTypeAliasDeclaration > TSTypeReference:not(:has(.typeParameters)) > Identifier', message: 'No renamed types', }, { @@ -133,7 +134,7 @@ const RESTRICTED_SYNTAXES = [ }, { // Matches non-functions. - selector: `:matches(Program, ExportNamedDeclaration, TSModuleBlock) > VariableDeclaration[kind=const] > VariableDeclarator[id.name=${NOT_CONSTANT_CASE}]:not(:has(:matches(ArrowFunctionExpression)))`, + selector: `:matches(Program, ExportNamedDeclaration, TSModuleBlock) > VariableDeclaration[kind=const] > VariableDeclarator[id.name=${NOT_CONSTANT_CASE}]:not(:matches(:has(ArrowFunctionExpression), :has(CallExpression[callee.object.name=newtype][callee.property.name=newtypeConstructor])))`, message: 'Use `CONSTANT_CASE` for top-level constants that are not functions', }, { @@ -225,6 +226,10 @@ const RESTRICTED_SYNTAXES = [ selector: 'IfStatement > ExpressionStatement', message: 'Wrap `if` branches in `{}`', }, + { + selector: ':matches(ForStatement[test=null], ForStatement[test.value=true])', + message: 'Use `while (true)` instead of `for (;;)`', + }, { selector: 'VariableDeclarator[id.name=ENVIRONMENT][init.value!=production]', message: "Environment must be 'production' when committing", @@ -290,6 +295,7 @@ export default [ }, ], 'sort-imports': ['error', { allowSeparatedGroups: true }], + 'no-constant-condition': ['error', { checkLoops: false }], 'no-restricted-properties': [ 'error', { @@ -357,7 +363,10 @@ export default [ { checksVoidReturn: { attributes: false } }, ], '@typescript-eslint/no-redundant-type-constituents': 'error', - '@typescript-eslint/no-unnecessary-condition': 'error', + '@typescript-eslint/no-unnecessary-condition': [ + 'error', + { allowConstantLoopConditions: true }, + ], '@typescript-eslint/no-useless-empty-export': 'error', '@typescript-eslint/parameter-properties': ['error', { prefer: 'parameter-property' }], '@typescript-eslint/prefer-enum-initializers': 'error', diff --git a/app/ide-desktop/lib/assets/cross.svg b/app/ide-desktop/lib/assets/cross.svg new file mode 100644 index 00000000000..9c90f46ac77 --- /dev/null +++ b/app/ide-desktop/lib/assets/cross.svg @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/app/ide-desktop/lib/assets/tick.svg b/app/ide-desktop/lib/assets/tick.svg new file mode 100644 index 00000000000..6d0455cac8b --- /dev/null +++ b/app/ide-desktop/lib/assets/tick.svg @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/ide-desktop/lib/client/src/preload.ts b/app/ide-desktop/lib/client/src/preload.ts index b7024e33c64..5ed5444aaac 100644 --- a/app/ide-desktop/lib/client/src/preload.ts +++ b/app/ide-desktop/lib/client/src/preload.ts @@ -77,6 +77,8 @@ electron.contextBridge.exposeInMainWorld('enso_console', { // === Authentication API === // ========================== +let currentDeepLinkHandler: ((event: Electron.IpcRendererEvent, url: string) => void) | null = null + /** Object exposed on the Electron main window; provides proxy functions to: * - open OAuth flows in the system browser, and * - handle deep links from the system browser or email client to the dashboard. @@ -102,10 +104,15 @@ const AUTHENTICATION_API = { * `enso://authentication/register?code=...&state=...` from external sources like the user's * system browser or email client. Handling the links involves resuming whatever flow was in * progress when the link was opened (e.g., an OAuth registration flow). */ - setDeepLinkHandler: (callback: (url: string) => void) => - electron.ipcRenderer.on(ipc.Channel.openDeepLink, (_event, url: string) => { + setDeepLinkHandler: (callback: (url: string) => void) => { + if (currentDeepLinkHandler != null) { + electron.ipcRenderer.off(ipc.Channel.openDeepLink, currentDeepLinkHandler) + } + currentDeepLinkHandler = (_event, url: string) => { callback(url) - }), + } + electron.ipcRenderer.on(ipc.Channel.openDeepLink, currentDeepLinkHandler) + }, /** Save the access token to a credentials file. * * The backend doesn't have access to Electron's `localStorage` so we need to save access token diff --git a/app/ide-desktop/lib/client/src/project-management.ts b/app/ide-desktop/lib/client/src/project-management.ts index 25ec990d4e7..665f6a51fe9 100644 --- a/app/ide-desktop/lib/client/src/project-management.ts +++ b/app/ide-desktop/lib/client/src/project-management.ts @@ -231,7 +231,8 @@ export function generateDirectoryName(name: string): string { // If the name already consists a suffix, reuse it. const matches = name.match(/^(.*)_(\d+)$/) - let suffix = 0 + const initialSuffix = -1 + let suffix = initialSuffix // Matches start with the whole match, so we need to skip it. Then come our two capture groups. const [matchedName, matchedSuffix] = matches?.slice(1) ?? [] if (typeof matchedName !== 'undefined' && typeof matchedSuffix !== 'undefined') { @@ -240,7 +241,8 @@ export function generateDirectoryName(name: string): string { } const projectsDirectory = getProjectsDirectory() - for (; ; suffix++) { + while (true) { + suffix++ const candidatePath = pathModule.join( projectsDirectory, `${name}${suffix === 0 ? '' : `_${suffix}`}` diff --git a/app/ide-desktop/lib/client/watch.ts b/app/ide-desktop/lib/client/watch.ts index bf6775ac5e9..15370fcd718 100644 --- a/app/ide-desktop/lib/client/watch.ts +++ b/app/ide-desktop/lib/client/watch.ts @@ -142,7 +142,7 @@ process.on('SIGINT', () => { process.exit(0) }) -for (;;) { +while (true) { console.log('Spawning Electron process.') const electronProcess = childProcess.spawn('electron', ELECTRON_ARGS, { stdio: 'inherit', diff --git a/app/ide-desktop/lib/common/src/detect.ts b/app/ide-desktop/lib/common/src/detect.ts index 9d82a1884bb..7374d53d033 100644 --- a/app/ide-desktop/lib/common/src/detect.ts +++ b/app/ide-desktop/lib/common/src/detect.ts @@ -11,3 +11,29 @@ export function isRunningInElectron() { return /electron/i.test(navigator.userAgent) } + +// ================ +// === Platform === +// ================ + +/** Possible platforms that the app may run on. */ +export enum Platform { + unknown = 'Unknown platform', + windows = 'Windows', + macOS = 'macOS', + linux = 'Linux', +} + +/** Returns the platform the app is currently running on. + * This is used to determine whether `metaKey` or `ctrlKey` is used in shortcuts. */ +export function platform(): Platform { + if (/windows/i.test(navigator.userAgent)) { + return Platform.windows + } else if (/mac os/i.test(navigator.userAgent)) { + return Platform.macOS + } else if (/linux/i.test(navigator.userAgent)) { + return Platform.linux + } else { + return Platform.unknown + } +} diff --git a/app/ide-desktop/lib/content/src/index.html b/app/ide-desktop/lib/content/src/index.html index 87bdfb12d80..b1f05d35170 100644 --- a/app/ide-desktop/lib/content/src/index.html +++ b/app/ide-desktop/lib/content/src/index.html @@ -33,11 +33,11 @@ user-scalable = no" /> Enso - - - - - + + + + +