From 2b779ca1c4fe342e28e27b268bb565236f0619a6 Mon Sep 17 00:00:00 2001 From: somebody1234 Date: Sat, 23 Sep 2023 01:56:12 +1000 Subject: [PATCH 01/19] Fix table footer in firefox --- .../src/authentication/src/dashboard/components/assetsTable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/assetsTable.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/assetsTable.tsx index 5794ce692210..d978d3dcd317 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/assetsTable.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/assetsTable.tsx @@ -856,7 +856,7 @@ export default function AssetsTable(props: AssetsTableProps) { AssetRowState, backendModule.AssetId > - footer={} + footer={} scrollContainerRef={scrollContainerRef} headerRowRef={headerRowRef} rowComponent={AssetRow} From db72fa50a329af4262c9a543e51d845b434077a0 Mon Sep 17 00:00:00 2001 From: somebody1234 Date: Sat, 23 Sep 2023 02:19:56 +1000 Subject: [PATCH 02/19] Properly fix issue --- .../src/dashboard/components/assetsTable.tsx | 1 - .../src/dashboard/components/table.tsx | 35 +++++++++---------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/assetsTable.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/assetsTable.tsx index d978d3dcd317..40191b4d8900 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/assetsTable.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/assetsTable.tsx @@ -856,7 +856,6 @@ export default function AssetsTable(props: AssetsTableProps) { AssetRowState, backendModule.AssetId > - footer={} scrollContainerRef={scrollContainerRef} headerRowRef={headerRowRef} rowComponent={AssetRow} diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/table.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/table.tsx index 2427c8e5d0ca..bf62393de851 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/table.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/table.tsx @@ -50,7 +50,6 @@ interface InternalNoSelectedKeysProps { /** Props for a {@link Table}. */ interface InternalTableProps { - footer?: JSX.Element rowComponent?: (props: tableRow.TableRowProps) => JSX.Element | null scrollContainerRef?: React.RefObject headerRowRef?: React.RefObject @@ -67,7 +66,7 @@ interface InternalTableProps, - event: React.MouseEvent, + event: React.MouseEvent, setSelectedKeys: (items: Set) => void ) => void } @@ -88,7 +87,6 @@ export default function Table ) { const { - footer, rowComponent: RowComponent = TableRow, scrollContainerRef, headerRowRef, @@ -309,24 +307,25 @@ export default function Table { onContextMenu(selectedKeys, event, setSelectedKeys) }} > - {headerRow} - - {itemRows} - {placeholder && ( - - - {placeholder} - - - )} - - {footer} - + + {headerRow} + + {itemRows} + {placeholder && ( + + + + )} + +
+ {placeholder} +
+ ) } From ed57194d6edac819661b21687edb8faf2995249f Mon Sep 17 00:00:00 2001 From: somebody1234 Date: Tue, 26 Sep 2023 08:18:13 +1000 Subject: [PATCH 03/19] Fix missing error when passing invalid `startup.project` --- .../src/dashboard/components/assetsTable.tsx | 25 ++++++------------- 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/assetsTable.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/assetsTable.tsx index 40191b4d8900..faab59ff723a 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/assetsTable.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/assetsTable.tsx @@ -1,6 +1,5 @@ /** @file Table displaying a list of projects. */ import * as React from 'react' -import * as toastify from 'react-toastify' import * as array from '../array' import * as assetEventModule from '../events/assetEvent' @@ -22,7 +21,6 @@ import * as uniqueString from '../../uniqueString' import * as authProvider from '../../authentication/providers/auth' import * as backendProvider from '../../providers/backend' -import * as loggerProvider from '../../providers/logger' import * as modalProvider from '../../providers/modal' import AssetRow from './assetRow' @@ -189,11 +187,11 @@ export default function AssetsTable(props: AssetsTableProps) { isListingLocalDirectoryAndWillFail, isListingRemoteDirectoryAndWillFail, } = props - const logger = loggerProvider.useLogger() const { organization, user, accessToken } = authProvider.useNonPartialUserSession() const { backend } = backendProvider.useBackend() const { setModal } = modalProvider.useSetModal() const { localStorage } = localStorageProvider.useLocalStorage() + const toastAndLog = hooks.useToastAndLog() const [initialized, setInitialized] = React.useState(false) const [assetTree, setAssetTree] = React.useState([]) const [isLoading, setIsLoading] = React.useState(true) @@ -276,6 +274,7 @@ export default function AssetsTable(props: AssetsTableProps) { const overwriteAssets = React.useCallback( (newAssets: backendModule.AnyAsset[]) => { + setInitialized(true) setAssetTree( newAssets.map(asset => ({ key: asset.id, @@ -287,7 +286,8 @@ export default function AssetsTable(props: AssetsTableProps) { // The project name here might also be a string with project id, e.g. when opening // a project file from explorer on Windows. const isInitialProject = (asset: backendModule.AnyAsset) => - asset.title === initialProjectName || asset.id === initialProjectName + asset.title === nameOfProjectToImmediatelyOpen || + asset.id === nameOfProjectToImmediatelyOpen if (nameOfProjectToImmediatelyOpen != null) { const projectToLoad = newAssets .filter(backendModule.assetIsProject) @@ -299,6 +299,8 @@ export default function AssetsTable(props: AssetsTableProps) { shouldAutomaticallySwitchPage: true, runInBackground: false, }) + } else { + toastAndLog(`Could not find project '${nameOfProjectToImmediatelyOpen}'`) } setNameOfProjectToImmediatelyOpen(null) } @@ -312,24 +314,12 @@ export default function AssetsTable(props: AssetsTableProps) { } return [] }) - if (!initialized) { - setInitialized(true) - if (initialProjectName != null) { - if (!newAssets.some(isInitialProject)) { - const errorMessage = `No project named '${initialProjectName}' was found.` - toastify.toast.error(errorMessage) - logger.error(`Error opening project on startup: ${errorMessage}`) - } - } - } }, [ - initialized, - initialProjectName, - logger, nameOfProjectToImmediatelyOpen, /* should never change */ setNameOfProjectToImmediatelyOpen, /* should never change */ dispatchAssetEvent, + /* should never change */ toastAndLog, ] ) @@ -376,7 +366,6 @@ export default function AssetsTable(props: AssetsTableProps) { ) React.useEffect(() => { - setInitialized(true) const savedExtraColumns = localStorage.get(localStorageModule.LocalStorageKey.extraColumns) if (savedExtraColumns != null) { setExtraColumns(new Set(savedExtraColumns)) From 110dc3d46d6b1235bb96dd21e9007926a9e62a59 Mon Sep 17 00:00:00 2001 From: somebody1234 Date: Tue, 26 Sep 2023 08:31:28 +1000 Subject: [PATCH 04/19] Intercept "Enter" keypress when renaming projects; gracefully fail when `projectState` is missing --- .../components/directoryNameColumn.tsx | 5 +++++ .../components/projectNameColumn.tsx | 21 ++++++++++++++----- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/directoryNameColumn.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/directoryNameColumn.tsx index af4cf5bd4a82..b29775799195 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/directoryNameColumn.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/directoryNameColumn.tsx @@ -132,6 +132,11 @@ export default function DirectoryNameColumn(props: DirectoryNameColumnProps) { onMouseLeave={() => { setIsHovered(false) }} + onKeyDown={event => { + if (rowState.isEditingName && event.key === 'Enter') { + event.stopPropagation() + } + }} onClick={event => { if ( eventModule.isSingleClick(event) && diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/projectNameColumn.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/projectNameColumn.tsx index 7557fb98f273..46b75777dc08 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/projectNameColumn.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/projectNameColumn.tsx @@ -63,15 +63,19 @@ export default function ProjectNameColumn(props: ProjectNameColumnProps) { const ownPermission = asset.permissions?.find(permission => permission.user.user_email === organization?.email) ?? null - const isRunning = backendModule.DOES_PROJECT_STATE_INDICATE_VM_EXISTS[asset.projectState.type] + // This is a workaround for a temporary bad state in the backend causing the `projectState` key + // to be absent. + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + const projectState = asset.projectState ?? { type: backendModule.ProjectState.closed } + const isRunning = backendModule.DOES_PROJECT_STATE_INDICATE_VM_EXISTS[projectState.type] const canExecute = backend.type === backendModule.BackendType.local || (ownPermission != null && permissions.PERMISSION_ACTION_CAN_EXECUTE[ownPermission.permission]) const isOtherUserUsingProject = backend.type !== backendModule.BackendType.local && - asset.projectState.opened_by != null && - asset.projectState.opened_by !== organization?.email + projectState.opened_by != null && + projectState.opened_by !== organization?.email const doRename = async (newName: string) => { try { @@ -122,7 +126,7 @@ export default function ProjectNameColumn(props: ProjectNameColumnProps) { ...asset, id: createdProject.projectId, projectState: { - ...asset.projectState, + ...projectState, type: backendModule.ProjectState.placeholder, }, }) @@ -224,6 +228,11 @@ export default function ProjectNameColumn(props: ProjectNameColumnProps) { className={`flex text-left items-center whitespace-nowrap rounded-l-full gap-1 px-1.5 py-1 min-w-max ${indent.indentClass( item.depth )}`} + onKeyDown={event => { + if (rowState.isEditingName && event.key === 'Enter') { + event.stopPropagation() + } + }} onClick={event => { if (rowState.isEditingName || isOtherUserUsingProject) { // The project should neither be edited nor opened in these cases. @@ -260,7 +269,9 @@ export default function ProjectNameColumn(props: ProjectNameColumnProps) { ) : ( Date: Tue, 26 Sep 2023 08:35:02 +1000 Subject: [PATCH 05/19] Intercept "Enter" keypress on other asset types just in case --- .../src/dashboard/components/fileNameColumn.tsx | 5 +++++ .../src/dashboard/components/secretNameColumn.tsx | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/fileNameColumn.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/fileNameColumn.tsx index e2cb44f807ec..687e12491369 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/fileNameColumn.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/fileNameColumn.tsx @@ -105,6 +105,11 @@ export default function FileNameColumn(props: FileNameColumnProps) { className={`flex text-left items-center align-middle whitespace-nowrap rounded-l-full gap-1 px-1.5 py-1 min-w-max ${indent.indentClass( item.depth )}`} + onKeyDown={event => { + if (rowState.isEditingName && event.key === 'Enter') { + event.stopPropagation() + } + }} onClick={event => { if ( eventModule.isSingleClick(event) && diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/secretNameColumn.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/secretNameColumn.tsx index 2171ec518e93..027ae42f364b 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/secretNameColumn.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/secretNameColumn.tsx @@ -106,6 +106,11 @@ export default function SecretNameColumn(props: SecretNameColumnProps) { className={`flex text-left items-center whitespace-nowrap rounded-l-full gap-1 px-1.5 py-1 min-w-max ${indent.indentClass( item.depth )}`} + onKeyDown={event => { + if (rowState.isEditingName && event.key === 'Enter') { + event.stopPropagation() + } + }} onClick={event => { if ( eventModule.isSingleClick(event) && From cecaac5d550c613603e576af5454cc88c37324f0 Mon Sep 17 00:00:00 2001 From: somebody1234 Date: Tue, 26 Sep 2023 08:49:58 +1000 Subject: [PATCH 06/19] Avoid opening editor when `startup.project` is provided --- .../authentication/src/dashboard/components/dashboard.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/dashboard.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/dashboard.tsx index 6832ad706e6e..4e32ed669124 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/dashboard.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/dashboard.tsx @@ -109,6 +109,7 @@ export default function Dashboard(props: DashboardProps) { let currentBackend = backend if ( supportsLocalBackend && + initialProjectName == null && session.type !== authProvider.UserSessionType.offline && localStorage.get(localStorageModule.LocalStorageKey.backendType) === backendModule.BackendType.local @@ -122,7 +123,11 @@ export default function Dashboard(props: DashboardProps) { const savedProjectStartupInfo = localStorage.get( localStorageModule.LocalStorageKey.projectStartupInfo ) - if (savedProjectStartupInfo != null) { + if (initialProjectName != null) { + if (page === pageSwitcher.Page.editor) { + setPage(pageSwitcher.Page.drive) + } + } else if (savedProjectStartupInfo != null) { if (savedProjectStartupInfo.backendType === backendModule.BackendType.remote) { if (session.accessToken != null) { if ( From 82347da26868ed424060a4e7a405faedbb68a0cb Mon Sep 17 00:00:00 2001 From: somebody1234 Date: Tue, 26 Sep 2023 21:05:50 +1000 Subject: [PATCH 07/19] Restore ability to upload from right clicking non-directory assets --- .../src/dashboard/assetTreeNode.ts | 11 ++- .../dashboard/components/assetContextMenu.tsx | 22 +++-- .../src/dashboard/components/assetsTable.tsx | 92 ++++++++++++++++--- 3 files changed, 101 insertions(+), 24 deletions(-) diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/assetTreeNode.ts b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/assetTreeNode.ts index c93b656b7bef..3806cc4f0dbc 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/assetTreeNode.ts +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/assetTreeNode.ts @@ -9,11 +9,16 @@ import * as backendModule from './backend' /** A node in the drive's item tree. */ export interface AssetTreeNode { - /** The original id of the asset (the placeholder id for new assets). This must never change. */ + /** The id of the asset (or the placeholder id for new assets). This must never change. */ key: backendModule.AssetId /** The actual asset. This MAY change if this is initially a placeholder item, but rows MAY * keep updated values within the row itself as well. */ item: backendModule.AnyAsset + /** The id of the asset's parent directory (or the placeholder id for new assets). + * This must never change. */ + directoryKey: backendModule.AssetId | null + /** The actual id of the asset's parent directory (or the placeholder id for new assets). */ + directoryId: backendModule.DirectoryId | null /** This is `null` if the asset is not a directory asset, OR if it is a collapsed directory * asset. */ children: AssetTreeNode[] | null @@ -113,11 +118,15 @@ export function assetTreePreorderTraversal( /** Creates an {@link AssetTreeNode} from a {@link backendModule.AnyAsset}. */ export function assetTreeNodeFromAsset( asset: backendModule.AnyAsset, + directoryKey: backendModule.AssetId | null, + directoryId: backendModule.DirectoryId | null, depth: number ): AssetTreeNode { return { key: asset.id, item: asset, + directoryKey, + directoryId, children: null, depth, } diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/assetContextMenu.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/assetContextMenu.tsx index c54f47534336..66c03329c8d5 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/assetContextMenu.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/assetContextMenu.tsx @@ -311,16 +311,20 @@ export default function AssetContextMenu(props: AssetContextMenuProps) { }} /> - {asset.type === backendModule.AssetType.directory ? ( -