From 657be61c30565fcbd67271514e0caaf23d501c90 Mon Sep 17 00:00:00 2001 From: somebody1234 Date: Fri, 29 Sep 2023 23:40:02 +1000 Subject: [PATCH] (even more) Dashboard fixes (#7877) - Fixes display of the assets table in Firefox. Before this fix, the table header grows instead of the table footer. It seems like the correct behavior for tables is to [not be valid flex children](https://stackoverflow.com/a/41421700/3323231), so the correct fix is to add a `flex` wrapper div. - Fixes missing error toast notification when `startup.project` is invalid - Fixes #7589 - Enter (incorrectly) causes projects to be opened when the name is being edited - Gracefully fail when `projectState` is missing from an asset - Avoid opening editor for previously opened project, when `startup.project` is provided - Fixes https://github.com/enso-org/cloud-v2/issues/688 - Fixes CSS for search bar - Fixes https://github.com/enso-org/cloud-v2/issues/689 - Adds "New " entries back to context menus for regular assets - Fixes https://github.com/enso-org/cloud-v2/issues/692 - Fix `z-index` of top left (page switcher) icons - Fixes https://github.com/enso-org/cloud-v2/issues/693 - Fix restoring local projects after closing and reopening - Adds blur to top right menu bars for extra visibility # Important Notes Testing should test Firefox and Chrome (testing on Safari is optional) to make sure the following is working correctly: - The table looks normal - The context menu still triggers below the table (both with and without items being selected) - There is no scrollbar if the table is shorter than the viewport --- app/ide-desktop/lib/client/watch.ts | 1 - .../components/forgotPassword.tsx | 13 +- .../src/authentication/components/input.tsx | 2 +- .../src/authentication/components/login.tsx | 17 +- .../components/registration.tsx | 15 +- .../components/resetPassword.tsx | 19 +- .../authentication/components/setUsername.tsx | 4 +- .../src/authentication/providers/auth.tsx | 2 +- .../src/authentication/src/components/app.tsx | 2 +- .../src/dashboard/assetTreeNode.ts | 11 +- .../dashboard/components/assetContextMenu.tsx | 22 +- .../src/dashboard/components/assetInfoBar.tsx | 2 +- .../src/dashboard/components/assetsTable.tsx | 217 ++++++++++++------ .../components/changePasswordModal.tsx | 4 +- .../src/dashboard/components/contextMenus.tsx | 2 +- .../src/dashboard/components/dashboard.tsx | 45 +++- .../components/directoryNameColumn.tsx | 5 + .../src/dashboard/components/drive.tsx | 2 +- .../src/dashboard/components/driveBar.tsx | 2 +- .../dashboard/components/fileNameColumn.tsx | 5 + .../components/managePermissionsModal.tsx | 2 +- .../src/dashboard/components/modal.tsx | 5 +- .../src/dashboard/components/pageSwitcher.tsx | 6 +- .../components/permissionSelector.tsx | 2 +- .../components/projectNameColumn.tsx | 23 +- .../dashboard/components/secretNameColumn.tsx | 5 + .../src/dashboard/components/table.tsx | 35 ++- .../src/dashboard/components/topBar.tsx | 4 +- .../src/dashboard/components/userBar.tsx | 2 +- .../src/dashboard/components/userMenu.tsx | 2 +- .../src/dashboard/localBackend.ts | 26 +-- .../src/dashboard/remoteBackend.ts | 2 +- .../lib/dashboard/src/tailwind.css | 23 +- .../lib/dashboard/tailwind.config.ts | 2 + 34 files changed, 333 insertions(+), 198 deletions(-) diff --git a/app/ide-desktop/lib/client/watch.ts b/app/ide-desktop/lib/client/watch.ts index eaa4a0754ed1..dcce5893e581 100644 --- a/app/ide-desktop/lib/client/watch.ts +++ b/app/ide-desktop/lib/client/watch.ts @@ -86,7 +86,6 @@ const ALL_BUNDLES_READY = new Promise((resolve, reject) => { }, }) dashboardOpts.outdir = path.resolve(IDE_DIR_PATH, 'assets') - dashboardOpts.write = false const dashboardBuilder = await esbuild.context(dashboardOpts) const dashboard = await dashboardBuilder.rebuild() console.log('Result of dashboard bundling: ', dashboard) diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/components/forgotPassword.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/components/forgotPassword.tsx index 81f695e1e5b0..9241944eb6fb 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/components/forgotPassword.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/components/forgotPassword.tsx @@ -26,13 +26,8 @@ export default function ForgotPassword() { return (
-
-
+
+
Forgot Your Password?
@@ -45,7 +40,7 @@ export default function ForgotPassword() {
@@ -69,7 +64,7 @@ export default function ForgotPassword() { type="submit" className={ 'flex items-center justify-center focus:outline-none text-white text-sm ' + - 'sm:text-base bg-blue-600 hover:bg-blue-700 rounded py-2 w-full transition ' + + 'bg-blue-600 hover:bg-blue-700 rounded py-2 w-full transition ' + 'duration-150 ease-in' } > diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/components/input.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/components/input.tsx index 502c1c95cf15..56cf3eeb6761 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/components/input.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/components/input.tsx @@ -95,7 +95,7 @@ export default function Input(props: InputProps) { } : onBlur } - className="text-sm sm:text-base placeholder-gray-500 pl-10 pr-4 rounded-lg border border-gray-400 w-full py-2 focus:outline-none focus:border-blue-400" + className="text-sm placeholder-gray-500 pl-10 pr-4 rounded-lg border border-gray-400 w-full py-2 focus:outline-none focus:border-blue-400" /> ) } diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/components/login.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/components/login.tsx index dfc7d5f7c2cd..20a08d8cb859 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/components/login.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/components/login.tsx @@ -44,13 +44,8 @@ export default function Login() { return (
-
-
+
+
Login To Your Account
diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/contextMenus.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/contextMenus.tsx index 8da3bc05ba4e..254eb4e96589 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/contextMenus.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/contextMenus.tsx @@ -33,7 +33,7 @@ export default function ContextMenus(props: ContextMenusProps) { <>{children} ) : ( { innerEvent.preventDefault() }} 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..887f4bc48117 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 @@ -7,7 +7,7 @@ import * as assetListEventModule from '../events/assetListEvent' import * as backendModule from '../backend' import * as hooks from '../../hooks' import * as http from '../../http' -import * as localBackend from '../localBackend' +import * as localBackendModule from '../localBackend' import * as localStorageModule from '../localStorage' import * as projectManager from '../projectManager' import * as remoteBackendModule from '../remoteBackend' @@ -44,7 +44,12 @@ export interface DashboardProps { /** The component that contains the entire UI. */ export default function Dashboard(props: DashboardProps) { - const { supportsLocalBackend, appRunner, initialProjectName, projectManagerUrl } = props + const { + supportsLocalBackend, + appRunner, + initialProjectName: rawInitialProjectName, + projectManagerUrl, + } = props const logger = loggerProvider.useLogger() const session = authProvider.useNonPartialUserSession() const { backend } = backendProvider.useBackend() @@ -72,6 +77,7 @@ export default function Dashboard(props: DashboardProps) { hooks.useEvent() const [assetEvents, dispatchAssetEvent] = hooks.useEvent() const modalRef = React.useRef(null) + const [initialProjectName, setInitialProjectName] = React.useState(rawInitialProjectName) const isListingLocalDirectoryAndWillFail = backend.type === backendModule.BackendType.local && loadingProjectManagerDidFail @@ -109,20 +115,20 @@ export default function Dashboard(props: DashboardProps) { let currentBackend = backend if ( supportsLocalBackend && - session.type !== authProvider.UserSessionType.offline && localStorage.get(localStorageModule.LocalStorageKey.backendType) === backendModule.BackendType.local ) { - currentBackend = new localBackend.LocalBackend( - projectManagerUrl, - localStorage.get(localStorageModule.LocalStorageKey.projectStartupInfo) ?? null - ) + currentBackend = new localBackendModule.LocalBackend(projectManagerUrl) setBackend(currentBackend) } const savedProjectStartupInfo = localStorage.get( localStorageModule.LocalStorageKey.projectStartupInfo ) - if (savedProjectStartupInfo != null) { + if (rawInitialProjectName != 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 ( @@ -171,7 +177,26 @@ export default function Dashboard(props: DashboardProps) { } } } else { - setProjectStartupInfo(savedProjectStartupInfo) + if (currentBackend.type === backendModule.BackendType.local) { + setInitialProjectName(savedProjectStartupInfo.projectAsset.id) + } else { + const localBackend = new localBackendModule.LocalBackend(projectManagerUrl) + void (async () => { + await localBackend.openProject( + savedProjectStartupInfo.projectAsset.id, + null, + savedProjectStartupInfo.projectAsset.title + ) + const project = await localBackend.getProjectDetails( + savedProjectStartupInfo.projectAsset.id, + savedProjectStartupInfo.projectAsset.title + ) + setProjectStartupInfo({ + ...savedProjectStartupInfo, + project, + }) + })() + } } } // This MUST only run when the component is mounted. @@ -272,7 +297,7 @@ export default function Dashboard(props: DashboardProps) { if (newBackendType !== backend.type) { switch (newBackendType) { case backendModule.BackendType.local: - setBackend(new localBackend.LocalBackend(projectManagerUrl, null)) + setBackend(new localBackendModule.LocalBackend(projectManagerUrl)) break case backendModule.BackendType.remote: { const headers = new Headers() 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 a22d058c2e5b..543c822d1292 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 @@ -139,6 +139,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/drive.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/drive.tsx index f7fc5bbb529f..951a7722c530 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/drive.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/drive.tsx @@ -149,7 +149,7 @@ export default function Drive(props: DriveProps) { ) : isListingLocalDirectoryAndWillFail ? (
- Could not connect to the Project Manager. Please try restarting + Could not connect to the Project Manager. Please try restarting{' '} {common.PRODUCT_NAME}, or manually launching the Project Manager.
diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/driveBar.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/driveBar.tsx index 9930dfb5f869..72a40e0a4372 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/driveBar.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/driveBar.tsx @@ -75,7 +75,7 @@ export default function DriveBar(props: DriveBarProps) { }} > 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 877317a5a467..470b693907ab 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 @@ -107,6 +107,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/managePermissionsModal.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/managePermissionsModal.tsx index 8826e8c3e5cc..ff1593ec7068 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/managePermissionsModal.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/managePermissionsModal.tsx @@ -235,7 +235,7 @@ export default function ManagePermissionsModal< return (
+
{PAGE_DATA.map(pageData => { const isDisabled = pageData.page === page || (pageData.page === Page.editor && isEditorDisabled) diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/permissionSelector.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/permissionSelector.tsx index c0c39ae0c40f..5448bc1e3918 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/permissionSelector.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/permissionSelector.tsx @@ -94,7 +94,7 @@ export default function PermissionSelector(props: PermissionSelectorProps) { : function Child() { return ( { setTheChild(null) }} 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 dffea6280a2a..40df08070f7b 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 @@ -64,15 +64,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 { @@ -125,7 +129,7 @@ export default function ProjectNameColumn(props: ProjectNameColumnProps) { ...asset, id: createdProject.projectId, projectState: { - ...asset.projectState, + ...projectState, type: backendModule.ProjectState.placeholder, }, }) @@ -227,6 +231,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. @@ -263,7 +272,9 @@ export default function ProjectNameColumn(props: ProjectNameColumnProps) { ) : ( { + 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/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} +
+
) } diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/topBar.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/topBar.tsx index 7753555cb589..2f7bba45a01e 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/topBar.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/topBar.tsx @@ -74,7 +74,7 @@ export default function TopBar(props: TopBarProps) { return (
@@ -85,7 +85,7 @@ export default function TopBar(props: TopBarProps) {
{page !== pageSwitcher.Page.editor && ( <> -
+
diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/userBar.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/userBar.tsx index c397760e2e01..389d9b04cdfc 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/userBar.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/userBar.tsx @@ -57,7 +57,7 @@ export default function UserBar(props: UserBarProps) { setProjectAsset != null && self != null return ( -
+