From edb050c8e824212c542443efa67c219118620a8f Mon Sep 17 00:00:00 2001 From: somebody1234 Date: Wed, 8 May 2024 21:10:21 +1000 Subject: [PATCH 01/55] Refactor start page into a dialog; update Drive Bar styles --- app/ide-desktop/lib/assets/home.svg | 4 -- app/ide-desktop/lib/dashboard/e2e/actions.ts | 5 -- .../lib/dashboard/e2e/pageSwitcher.spec.ts | 7 --- .../AriaComponents/Dialog/Dialog.tsx | 10 ++- .../components/AriaComponents/Dialog/types.ts | 5 ++ .../src/components/styled/WindowButton.tsx | 40 ++++++++++++ .../lib/dashboard/src/layouts/AssetsTable.tsx | 5 +- .../src/layouts/CategorySwitcher.tsx | 18 +++--- .../src/layouts/CategorySwitcher/Category.ts | 2 +- .../lib/dashboard/src/layouts/Drive.tsx | 34 ++++------ .../lib/dashboard/src/layouts/DriveBar.tsx | 22 +++++-- .../dashboard/src/layouts/PageSwitcher.tsx | 4 -- .../lib/dashboard/src/layouts/Samples.tsx | 63 +------------------ .../src/layouts/{Home.tsx => Start.tsx} | 31 ++++----- .../lib/dashboard/src/layouts/WhatsNew.tsx | 2 +- .../src/pages/dashboard/Dashboard.tsx | 8 +-- .../lib/dashboard/src/tailwind.css | 13 ++-- .../lib/dashboard/src/text/english.json | 8 +-- .../lib/dashboard/tailwind.config.js | 10 +-- 19 files changed, 122 insertions(+), 169 deletions(-) delete mode 100644 app/ide-desktop/lib/assets/home.svg create mode 100644 app/ide-desktop/lib/dashboard/src/components/styled/WindowButton.tsx rename app/ide-desktop/lib/dashboard/src/layouts/{Home.tsx => Start.tsx} (52%) diff --git a/app/ide-desktop/lib/assets/home.svg b/app/ide-desktop/lib/assets/home.svg deleted file mode 100644 index 1b09b5562046..000000000000 --- a/app/ide-desktop/lib/assets/home.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/ide-desktop/lib/dashboard/e2e/actions.ts b/app/ide-desktop/lib/dashboard/e2e/actions.ts index 16c207078a7e..aadd1b616cfc 100644 --- a/app/ide-desktop/lib/dashboard/e2e/actions.ts +++ b/app/ide-desktop/lib/dashboard/e2e/actions.ts @@ -421,11 +421,6 @@ export function locateSortDescendingIcon(page: test.Locator | test.Page) { // === Page locators === -/** Find a "home page" icon (if any) on the current page. */ -export function locateHomePageIcon(page: test.Locator | test.Page) { - return page.getByAltText('Home tab') -} - /** Find a "drive page" icon (if any) on the current page. */ export function locateDrivePageIcon(page: test.Locator | test.Page) { return page.getByAltText('Drive tab') diff --git a/app/ide-desktop/lib/dashboard/e2e/pageSwitcher.spec.ts b/app/ide-desktop/lib/dashboard/e2e/pageSwitcher.spec.ts index 0a5fe940b15c..b39ca8e2849c 100644 --- a/app/ide-desktop/lib/dashboard/e2e/pageSwitcher.spec.ts +++ b/app/ide-desktop/lib/dashboard/e2e/pageSwitcher.spec.ts @@ -12,16 +12,9 @@ test.test('page switcher', async ({ page }) => { await actions.locateDrivePageIcon(page).click() await test.expect(actions.locateDriveView(page)).toBeVisible() - await test.expect(actions.locateSamplesList(page)).not.toBeVisible() - await test.expect(actions.locateEditor(page)).not.toBeVisible() - - await actions.locateHomePageIcon(page).click() - await test.expect(actions.locateDriveView(page)).not.toBeVisible() - await test.expect(actions.locateSamplesList(page)).toBeVisible() await test.expect(actions.locateEditor(page)).not.toBeVisible() await actions.locateEditorPageIcon(page).click() await test.expect(actions.locateDriveView(page)).not.toBeVisible() - await test.expect(actions.locateSamplesList(page)).not.toBeVisible() await test.expect(actions.locateEditor(page)).toBeVisible() }) diff --git a/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Dialog/Dialog.tsx b/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Dialog/Dialog.tsx index 982f74f7e2f2..f0e31b405828 100644 --- a/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Dialog/Dialog.tsx +++ b/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Dialog/Dialog.tsx @@ -12,6 +12,7 @@ import Dismiss from 'enso-assets/dismiss.svg' import * as aria from '#/components/aria' import * as ariaComponents from '#/components/AriaComponents' import * as portal from '#/components/Portal' +import WindowButton from '#/components/styled/WindowButton' import type * as types from './types' @@ -41,11 +42,13 @@ export function Dialog(props: types.DialogProps) { children, title, type = 'modal', + closeButton = 'none', isDismissible = true, isKeyboardDismissDisabled = false, className, ...ariaDialogProps } = props + const cleanupRef = React.useRef(() => {}) const root = portal.useStrictPortalContext() @@ -77,9 +80,14 @@ export function Dialog(props: types.DialogProps) { )} -
+
{typeof children === 'function' ? children(opts) : children}
+ {closeButton === 'floating' && ( +
+ +
+ )} )} diff --git a/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Dialog/types.ts b/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Dialog/types.ts index 9d48357f9c9d..961cbf9debdf 100644 --- a/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Dialog/types.ts +++ b/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Dialog/types.ts @@ -4,11 +4,16 @@ import type * as aria from '#/components/aria' /** The type of Dialog. */ export type DialogType = 'fullscreen' | 'modal' | 'popover' +/** The type of close button for the Dialog. + * Note that Dialogs with a title have a regular close button by default. */ +export type DialogCloseButtonType = 'floating' | 'none' + /** Props for the Dialog component. */ export interface DialogProps extends aria.DialogProps { /** The type of dialog to render. * @default 'modal' */ readonly type?: DialogType + readonly closeButton?: DialogCloseButtonType readonly title?: string readonly isDismissible?: boolean readonly onOpenChange?: (isOpen: boolean) => void diff --git a/app/ide-desktop/lib/dashboard/src/components/styled/WindowButton.tsx b/app/ide-desktop/lib/dashboard/src/components/styled/WindowButton.tsx new file mode 100644 index 000000000000..a8f3423c26a1 --- /dev/null +++ b/app/ide-desktop/lib/dashboard/src/components/styled/WindowButton.tsx @@ -0,0 +1,40 @@ +/** @file A button for performing an action on a window. */ +import * as React from 'react' + +import UnstyledButton from '#/components/UnstyledButton' + +// ================= +// === Constants === +// ================= + +const ROLE_CLASSES: Readonly> = { + close: 'hover:bg-window-close', +} + +// ======================== +// === WindowButtonRole === +// ======================== + +/** The role of a {@link WindowButton}. */ +export type WindowButtonRole = 'close' + +// ==================== +// === WindowButton === +// ==================== + +/** Props for a {@link WindowButton}. */ +export interface WindowButtonProps { + readonly onPress: () => void + readonly role: WindowButtonRole +} + +/** A button for performing an action on a window. */ +export default function WindowButton(props: WindowButtonProps) { + const { onPress, role } = props + return ( + + ) +} diff --git a/app/ide-desktop/lib/dashboard/src/layouts/AssetsTable.tsx b/app/ide-desktop/lib/dashboard/src/layouts/AssetsTable.tsx index d9fc2727bc49..83171fa39d5c 100644 --- a/app/ide-desktop/lib/dashboard/src/layouts/AssetsTable.tsx +++ b/app/ide-desktop/lib/dashboard/src/layouts/AssetsTable.tsx @@ -345,7 +345,6 @@ export interface AssetRowState { /** Props for a {@link AssetsTable}. */ export interface AssetsTableProps { readonly hidden: boolean - readonly hideRows: boolean readonly query: AssetQuery readonly setQuery: React.Dispatch> readonly setCanDownload: (canDownload: boolean) => void @@ -376,7 +375,7 @@ export interface AssetsTableProps { /** The table of project assets. */ export default function AssetsTable(props: AssetsTableProps) { - const { hidden, hideRows, query, setQuery, setCanDownload, category, allLabels } = props + const { hidden, query, setQuery, setCanDownload, category, allLabels } = props const { setSuggestions, deletedLabelNames, initialProjectName, projectStartupInfo } = props const { queuedAssetEvents: rawQueuedAssetEvents } = props const { assetListEvents, dispatchAssetListEvent, assetEvents, dispatchAssetEvent } = props @@ -2207,7 +2206,7 @@ export default function AssetsTable(props: AssetsTableProps) { columns={columns} item={item} state={state} - hidden={hideRows || visibilities.get(item.key) === Visibility.hidden} + hidden={hidden || visibilities.get(item.key) === Visibility.hidden} selected={isSelected} setSelected={selected => { setSelectedKeys(set.withPresence(selectedKeysRef.current, key, selected)) diff --git a/app/ide-desktop/lib/dashboard/src/layouts/CategorySwitcher.tsx b/app/ide-desktop/lib/dashboard/src/layouts/CategorySwitcher.tsx index 558fb77bbcac..e0d7d53ad1d3 100644 --- a/app/ide-desktop/lib/dashboard/src/layouts/CategorySwitcher.tsx +++ b/app/ide-desktop/lib/dashboard/src/layouts/CategorySwitcher.tsx @@ -41,13 +41,6 @@ interface CategoryMetadata { // ================= const CATEGORY_DATA: CategoryMetadata[] = [ - { - category: Category.recent, - icon: RecentIcon, - textId: 'recentCategory', - buttonTextId: 'recentCategoryButtonLabel', - dropZoneTextId: 'recentCategoryDropZoneLabel', - }, { category: Category.home, icon: Home2Icon, @@ -55,6 +48,13 @@ const CATEGORY_DATA: CategoryMetadata[] = [ buttonTextId: 'homeCategoryButtonLabel', dropZoneTextId: 'homeCategoryDropZoneLabel', }, + { + category: Category.recent, + icon: RecentIcon, + textId: 'recentCategory', + buttonTextId: 'recentCategoryButtonLabel', + dropZoneTextId: 'recentCategoryDropZoneLabel', + }, { category: Category.trash, icon: Trash2Icon, @@ -100,8 +100,8 @@ function CategorySwitcherItem(props: InternalCategorySwitcherItemProps) { >
void readonly supportsLocalBackend: boolean readonly hidden: boolean - readonly hideRows: boolean readonly initialProjectName: string | null /** These events will be dispatched the next time the assets list is refreshed, rather than * immediately. */ @@ -94,7 +93,7 @@ export interface DriveProps { /** Contains directory path and directory contents (projects, folders, secrets and files). */ export default function Drive(props: DriveProps) { - const { supportsLocalBackend, hidden, hideRows, initialProjectName, queuedAssetEvents } = props + const { supportsLocalBackend, hidden, initialProjectName, queuedAssetEvents } = props const { query, setQuery, labels, setLabels, setSuggestions, projectStartupInfo } = props const { assetListEvents, dispatchAssetListEvent, assetEvents, dispatchAssetEvent } = props const { setAssetPanelProps, doOpenEditor, doCloseEditor } = props @@ -358,25 +357,17 @@ export default function Drive(props: DriveProps) { hidden ? 'hidden' : '' }`} > -
- - {isCloud ? getText('cloudDrive') : getText('localDrive')} - - -
+
{isCloud && (
@@ -399,7 +390,6 @@ export default function Drive(props: DriveProps) { )}
-
From d4fdeeaf527b1c4f9d0320cbe75cffed965ca467 Mon Sep 17 00:00:00 2001 From: somebody1234 Date: Fri, 10 May 2024 19:00:18 +1000 Subject: [PATCH 12/55] Fix type errors --- app/ide-desktop/lib/dashboard/src/layouts/Settings.tsx | 2 +- .../src/layouts/Settings/UserGroupsSettingsTab.tsx | 1 + app/ide-desktop/lib/dashboard/src/layouts/UserBar.tsx | 6 ++++-- .../lib/dashboard/src/modals/NewUserGroupModal.tsx | 6 +++--- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/app/ide-desktop/lib/dashboard/src/layouts/Settings.tsx b/app/ide-desktop/lib/dashboard/src/layouts/Settings.tsx index e4819a763dc1..b07f27703b02 100644 --- a/app/ide-desktop/lib/dashboard/src/layouts/Settings.tsx +++ b/app/ide-desktop/lib/dashboard/src/layouts/Settings.tsx @@ -91,7 +91,7 @@ export default function Settings(props: SettingsProps) { break } case SettingsTab.userGroups: { - content = + content = backend == null ? null : break } case SettingsTab.keyboardShortcuts: { diff --git a/app/ide-desktop/lib/dashboard/src/layouts/Settings/UserGroupsSettingsTab.tsx b/app/ide-desktop/lib/dashboard/src/layouts/Settings/UserGroupsSettingsTab.tsx index f538acb14b38..306ba02dff8a 100644 --- a/app/ide-desktop/lib/dashboard/src/layouts/Settings/UserGroupsSettingsTab.tsx +++ b/app/ide-desktop/lib/dashboard/src/layouts/Settings/UserGroupsSettingsTab.tsx @@ -225,6 +225,7 @@ export default function UserGroupsSettingsTab(props: UserGroupsSettingsTabProps) const position = { pageX: rect.left, pageY: rect.top } setModal( { diff --git a/app/ide-desktop/lib/dashboard/src/layouts/UserBar.tsx b/app/ide-desktop/lib/dashboard/src/layouts/UserBar.tsx index bd49aa16d68b..9526f8b34efd 100644 --- a/app/ide-desktop/lib/dashboard/src/layouts/UserBar.tsx +++ b/app/ide-desktop/lib/dashboard/src/layouts/UserBar.tsx @@ -83,8 +83,10 @@ export default function UserBar(props: UserBarProps) { {shouldShowInviteButton && ( { - setModal() + onPress={event => { + const rect = event.target.getBoundingClientRect() + const position = { pageX: rect.left, pageY: rect.top } + setModal() }} > {getText('invite')} diff --git a/app/ide-desktop/lib/dashboard/src/modals/NewUserGroupModal.tsx b/app/ide-desktop/lib/dashboard/src/modals/NewUserGroupModal.tsx index c08b1f3b77aa..f6c48bc31e3d 100644 --- a/app/ide-desktop/lib/dashboard/src/modals/NewUserGroupModal.tsx +++ b/app/ide-desktop/lib/dashboard/src/modals/NewUserGroupModal.tsx @@ -3,7 +3,6 @@ import * as React from 'react' import * as toastAndLogHooks from '#/hooks/toastAndLogHooks' -import * as backendProvider from '#/providers/BackendProvider' import * as modalProvider from '#/providers/ModalProvider' import * as textProvider from '#/providers/TextProvider' @@ -13,6 +12,7 @@ import ButtonRow from '#/components/styled/ButtonRow' import UnstyledButton from '#/components/UnstyledButton' import type * as backendModule from '#/services/Backend' +import type Backend from '#/services/Backend' import * as eventModule from '#/utilities/event' import * as string from '#/utilities/string' @@ -23,6 +23,7 @@ import * as string from '#/utilities/string' /** Props for a {@link NewUserGroupModal}. */ export interface NewUserGroupModalProps { + readonly backend: Backend readonly event?: Pick readonly userGroups: backendModule.UserGroupInfo[] | null readonly onSubmit: (name: string) => void @@ -32,9 +33,8 @@ export interface NewUserGroupModalProps { /** A modal to create a user group. */ export default function NewUserGroupModal(props: NewUserGroupModalProps) { - const { userGroups: userGroupsRaw, onSubmit: onSubmitRaw, onSuccess, onFailure } = props + const { backend, userGroups: userGroupsRaw, onSubmit: onSubmitRaw, onSuccess, onFailure } = props const { event: positionEvent } = props - const { backend } = backendProvider.useBackend() const { unsetModal } = modalProvider.useSetModal() const { getText } = textProvider.useText() const toastAndLog = toastAndLogHooks.useToastAndLog() From 48a24d74cb61889c5d3f767f52fa61fd91cf43eb Mon Sep 17 00:00:00 2001 From: somebody1234 Date: Fri, 10 May 2024 19:01:00 +1000 Subject: [PATCH 13/55] Fix lints --- .../lib/dashboard/src/layouts/Settings/MembersTable.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/ide-desktop/lib/dashboard/src/layouts/Settings/MembersTable.tsx b/app/ide-desktop/lib/dashboard/src/layouts/Settings/MembersTable.tsx index 59d6fcae6e2e..4c80a870d7d1 100644 --- a/app/ide-desktop/lib/dashboard/src/layouts/Settings/MembersTable.tsx +++ b/app/ide-desktop/lib/dashboard/src/layouts/Settings/MembersTable.tsx @@ -5,7 +5,6 @@ import * as reactQuery from '@tanstack/react-query' import * as mimeTypes from '#/data/mimeTypes' -import * as asyncEffectHooks from '#/hooks/asyncEffectHooks' import * as scrollHooks from '#/hooks/scrollHooks' import * as toastAndLogHooks from '#/hooks/toastAndLogHooks' @@ -15,9 +14,9 @@ import * as textProvider from '#/providers/TextProvider' import UserRow from '#/layouts/Settings/UserRow' import * as aria from '#/components/aria' -import StatelessSpinner, * as statelessSpinner from '#/components/StatelessSpinner' -import Backend, * as backendModule from '#/services/Backend' +import * as backendModule from '#/services/Backend' +import type Backend from '#/services/Backend' // ==================== // === MembersTable === From dad5b03c62caaf1765ff5c1b70cbdca68885d8cf Mon Sep 17 00:00:00 2001 From: somebody1234 Date: Sat, 11 May 2024 01:20:06 +1000 Subject: [PATCH 14/55] Prettier --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index de679d88670a..228d56f367fc 100644 --- a/README.md +++ b/README.md @@ -207,11 +207,11 @@ Enso consists of several sub projects: command line tools. - **Enso IDE:** The - [Enso IDE](https://github.com/enso-org/enso/tree/develop/app/gui2) is a desktop - application that allows working with the visual form of Enso. It consists of - an Electron application, a high performance WebGL UI framework, and the - searcher which provides contextual search, hints, and documentation for all of - Enso's functionality. + [Enso IDE](https://github.com/enso-org/enso/tree/develop/app/gui2) is a + desktop application that allows working with the visual form of Enso. It + consists of an Electron application, a high performance WebGL UI framework, + and the searcher which provides contextual search, hints, and documentation + for all of Enso's functionality.
From 347b1a8ce0dd8ebb2e2b238938af52f823b310fd Mon Sep 17 00:00:00 2001 From: somebody1234 Date: Mon, 13 May 2024 16:24:46 +1000 Subject: [PATCH 15/55] Fix E2E tests --- app/ide-desktop/lib/dashboard/e2e/actions.ts | 20 +++++++++--- .../lib/dashboard/e2e/delete.spec.ts | 4 +-- .../lib/dashboard/e2e/startModal.spec.ts | 14 ++++++++ app/ide-desktop/lib/dashboard/package.json | 1 + .../lib/dashboard/playwright.config.ts | 4 +-- .../lib/dashboard/src/layouts/Start.tsx | 32 ++++++++++++------- app/ide-desktop/lib/types/globals.d.ts | 2 ++ 7 files changed, 58 insertions(+), 19 deletions(-) create mode 100644 app/ide-desktop/lib/dashboard/e2e/startModal.spec.ts diff --git a/app/ide-desktop/lib/dashboard/e2e/actions.ts b/app/ide-desktop/lib/dashboard/e2e/actions.ts index f7222f09402c..2a1edfd992a6 100644 --- a/app/ide-desktop/lib/dashboard/e2e/actions.ts +++ b/app/ide-desktop/lib/dashboard/e2e/actions.ts @@ -167,9 +167,14 @@ export function locateLabelsPanelLabels(page: test.Page) { ) } -/** Find a "home" button (if any) on the current page. */ -export function locateHomeButton(page: test.Locator | test.Page) { - return page.getByRole('button', { name: 'Home' }).getByText('Home') +/** Find a "cloud" category button (if any) on the current page. */ +export function locateCloudButton(page: test.Locator | test.Page) { + return page.getByRole('button', { name: 'Cloud' }).getByText('Cloud') +} + +/** Find a "local" category button (if any) on the current page. */ +export function locateLocalButton(page: test.Locator | test.Page) { + return page.getByRole('button', { name: 'Local' }).getByText('Local') } /** Find a "trash" button (if any) on the current page. */ @@ -331,9 +336,16 @@ export function locateUploadFilesButton(page: test.Locator | test.Page) { return page.getByRole('button', { name: 'Upload Files' }).getByText('Upload Files') } +/** Find a "start modal" button (if any) on the current page. */ +export function locateStartModalButton(page: test.Locator | test.Page) { + return page + .getByRole('button', { name: 'Start with a template' }) + .getByText('Start with a template') +} + /** Find a "new project" button (if any) on the current page. */ export function locateNewProjectButton(page: test.Locator | test.Page) { - return page.getByRole('button', { name: 'New Project' }).getByText('New Project') + return page.getByRole('button', { name: 'New Empty Project' }).getByText('New Empty Project') } /** Find a "new folder" button (if any) on the current page. */ diff --git a/app/ide-desktop/lib/dashboard/e2e/delete.spec.ts b/app/ide-desktop/lib/dashboard/e2e/delete.spec.ts index dd381bda2865..8f567afd8045 100644 --- a/app/ide-desktop/lib/dashboard/e2e/delete.spec.ts +++ b/app/ide-desktop/lib/dashboard/e2e/delete.spec.ts @@ -24,7 +24,7 @@ test.test('delete and restore', async ({ page }) => { await actions.locateRestoreFromTrashButton(contextMenu).click() await actions.expectTrashPlaceholderRow(page) - await actions.locateHomeButton(page).click() + await actions.locateCloudButton(page).click() await test.expect(assetRows).toHaveCount(1) }) @@ -45,6 +45,6 @@ test.test('delete and restore (keyboard)', async ({ page }) => { await actions.press(page, 'Mod+R') await actions.expectTrashPlaceholderRow(page) - await actions.locateHomeButton(page).click() + await actions.locateCloudButton(page).click() await test.expect(assetRows).toHaveCount(1) }) diff --git a/app/ide-desktop/lib/dashboard/e2e/startModal.spec.ts b/app/ide-desktop/lib/dashboard/e2e/startModal.spec.ts new file mode 100644 index 000000000000..0b3f9f63203a --- /dev/null +++ b/app/ide-desktop/lib/dashboard/e2e/startModal.spec.ts @@ -0,0 +1,14 @@ +/** @file Test the "change password" modal. */ +import * as test from '@playwright/test' + +import * as actions from './actions' + +test.test.beforeEach(actions.mockAllAndLogin) + +test.test('create project from template', async ({ page }) => { + await actions.locateStartModalButton(page).click() + // The second "sample" is the first template. + await actions.locateSamples(page).nth(1).click() + await test.expect(actions.locateEditor(page)).toBeVisible() + await test.expect(actions.locateSamples(page).first()).not.toBeVisible() +}) diff --git a/app/ide-desktop/lib/dashboard/package.json b/app/ide-desktop/lib/dashboard/package.json index cfc49468b542..8c0a99837799 100644 --- a/app/ide-desktop/lib/dashboard/package.json +++ b/app/ide-desktop/lib/dashboard/package.json @@ -19,6 +19,7 @@ "build": "vite build", "dev": "vite", "dev:e2e": "vite -c vite.test.config.ts", + "dev:e2e:ci": "vite -c vite.test.config.ts build && vite preview --port 8080 --strictPort", "test": "npm run test:unit && npm run test:e2e", "test:unit": "vitest run", "test:unit:debug": "vitest", diff --git a/app/ide-desktop/lib/dashboard/playwright.config.ts b/app/ide-desktop/lib/dashboard/playwright.config.ts index 971c19546b7c..0465f2a723d5 100644 --- a/app/ide-desktop/lib/dashboard/playwright.config.ts +++ b/app/ide-desktop/lib/dashboard/playwright.config.ts @@ -16,7 +16,7 @@ export default test.defineConfig({ testDir: './e2e', fullyParallel: true, forbidOnly: true, - workers: 1, + workers: process.env.PROD ? 8 : 1, repeatEach: process.env.CI ? 3 : 1, expect: { toHaveScreenshot: { threshold: 0 }, @@ -50,7 +50,7 @@ export default test.defineConfig({ }, }, webServer: { - command: 'npm run dev:e2e', + command: process.env.CI || process.env.PROD ? 'npm run dev:e2e:ci' : 'npm run dev:e2e', port: 8080, reuseExistingServer: false, }, diff --git a/app/ide-desktop/lib/dashboard/src/layouts/Start.tsx b/app/ide-desktop/lib/dashboard/src/layouts/Start.tsx index 07139840f993..8c541f5c800d 100644 --- a/app/ide-desktop/lib/dashboard/src/layouts/Start.tsx +++ b/app/ide-desktop/lib/dashboard/src/layouts/Start.tsx @@ -20,20 +20,30 @@ export interface HomeProps { /** Home screen. */ export default function Home(props: HomeProps) { - const { createProject } = props + const { createProject: createProjectRaw } = props const { getText } = textProvider.useText() + return ( -
- - {getText('welcomeSubtitle')} - - - -
+ {opts => ( +
+ + + {getText('welcomeSubtitle')} + + + + { + createProjectRaw(templateId, templateName) + opts.close() + }} + /> +
+ )}
) } diff --git a/app/ide-desktop/lib/types/globals.d.ts b/app/ide-desktop/lib/types/globals.d.ts index 03ec1ac81af5..368be7140bcc 100644 --- a/app/ide-desktop/lib/types/globals.d.ts +++ b/app/ide-desktop/lib/types/globals.d.ts @@ -104,6 +104,8 @@ declare global { // @ts-expect-error The index signature is intentional to disallow unknown env vars. readonly CI?: string // @ts-expect-error The index signature is intentional to disallow unknown env vars. + readonly PROD?: string + // @ts-expect-error The index signature is intentional to disallow unknown env vars. readonly CSC_LINK?: string // @ts-expect-error The index signature is intentional to disallow unknown env vars. readonly APPLEID?: string From bcff2e51debf147ce3c1eef2d46a5ba4d5e8cd18 Mon Sep 17 00:00:00 2001 From: somebody1234 Date: Mon, 13 May 2024 20:13:34 +1000 Subject: [PATCH 16/55] Fix ESLint config --- app/ide-desktop/eslint.config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/ide-desktop/eslint.config.js b/app/ide-desktop/eslint.config.js index de5deb2be37b..d394fdc4bfd0 100644 --- a/app/ide-desktop/eslint.config.js +++ b/app/ide-desktop/eslint.config.js @@ -240,8 +240,8 @@ const RESTRICTED_SYNTAXES = [ export default [ eslintJs.configs.recommended, { - // Playwright build cache. - ignores: ['**/.cache/**', '**/playwright-report'], + // Playwright build cache and Vite output. + ignores: ['**/.cache/**', '**/playwright-report', 'dist'], }, { settings: { From 380f7e27edebb4222e7a6d951d3748f8b80efa80 Mon Sep 17 00:00:00 2001 From: somebody1234 Date: Tue, 14 May 2024 05:38:43 +1000 Subject: [PATCH 17/55] Lighten extra columns for assets table --- .../lib/dashboard/src/components/styled/Button.tsx | 5 ++++- app/ide-desktop/lib/dashboard/src/layouts/AssetsTable.tsx | 2 +- app/ide-desktop/lib/dashboard/tailwind.config.js | 3 +++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/ide-desktop/lib/dashboard/src/components/styled/Button.tsx b/app/ide-desktop/lib/dashboard/src/components/styled/Button.tsx index 6b9fe9b273d9..2503ecba1072 100644 --- a/app/ide-desktop/lib/dashboard/src/components/styled/Button.tsx +++ b/app/ide-desktop/lib/dashboard/src/components/styled/Button.tsx @@ -19,6 +19,8 @@ export interface ButtonProps { /** Falls back to `aria-label`. Pass `false` to explicitly disable the tooltip. */ readonly tooltip?: React.ReactNode readonly autoFocus?: boolean + /** When `true`, the button uses a lighter color when it is not active. */ + readonly light?: boolean /** When `true`, the button is not faded out even when not hovered. */ readonly active?: boolean /** When `true`, the button is clickable, but displayed as not clickable. @@ -42,6 +44,7 @@ export interface ButtonProps { function Button(props: ButtonProps, ref: React.ForwardedRef) { const { tooltip, + light = false, active = false, softDisabled = false, image, @@ -68,7 +71,7 @@ function Button(props: ButtonProps, ref: React.ForwardedRef) })} >
(
- {willInviteNewUser ? getText('invite') : getText('share')}
- + )} diff --git a/app/ide-desktop/lib/dashboard/src/modals/NewLabelModal.tsx b/app/ide-desktop/lib/dashboard/src/modals/NewLabelModal.tsx index 897ea5353bc6..b4ca0d5d2253 100644 --- a/app/ide-desktop/lib/dashboard/src/modals/NewLabelModal.tsx +++ b/app/ide-desktop/lib/dashboard/src/modals/NewLabelModal.tsx @@ -10,12 +10,12 @@ import * as modalProvider from '#/providers/ModalProvider' import * as textProvider from '#/providers/TextProvider' import * as aria from '#/components/aria' +import * as ariaComponents from '#/components/AriaComponents' import ColorPicker from '#/components/ColorPicker' import Modal from '#/components/Modal' import ButtonRow from '#/components/styled/ButtonRow' import FocusArea from '#/components/styled/FocusArea' import FocusRing from '#/components/styled/FocusRing' -import UnstyledButton from '#/components/UnstyledButton' import * as backendModule from '#/services/Backend' import type Backend from '#/services/Backend' @@ -131,16 +131,23 @@ export default function NewLabelModal(props: NewLabelModalProps) { )} - {getText('create')} - - + + {getText('cancel')} - + diff --git a/app/ide-desktop/lib/dashboard/src/modals/NewUserGroupModal.tsx b/app/ide-desktop/lib/dashboard/src/modals/NewUserGroupModal.tsx index 955b022afeb7..a7b6147e338c 100644 --- a/app/ide-desktop/lib/dashboard/src/modals/NewUserGroupModal.tsx +++ b/app/ide-desktop/lib/dashboard/src/modals/NewUserGroupModal.tsx @@ -10,9 +10,9 @@ import * as modalProvider from '#/providers/ModalProvider' import * as textProvider from '#/providers/TextProvider' import * as aria from '#/components/aria' +import * as ariaComponents from '#/components/AriaComponents' import Modal from '#/components/Modal' import ButtonRow from '#/components/styled/ButtonRow' -import UnstyledButton from '#/components/UnstyledButton' import type Backend from '#/services/Backend' @@ -110,16 +110,23 @@ export default function NewUserGroupModal(props: NewUserGroupModalProps) { {nameError} - {getText('create')} - - + + {getText('cancel')} - + diff --git a/app/ide-desktop/lib/dashboard/src/modals/UpsertDatalinkModal.tsx b/app/ide-desktop/lib/dashboard/src/modals/UpsertDatalinkModal.tsx index 793da52bcdd7..ecdc6f94400f 100644 --- a/app/ide-desktop/lib/dashboard/src/modals/UpsertDatalinkModal.tsx +++ b/app/ide-desktop/lib/dashboard/src/modals/UpsertDatalinkModal.tsx @@ -10,12 +10,12 @@ import * as modalProvider from '#/providers/ModalProvider' import * as textProvider from '#/providers/TextProvider' import * as aria from '#/components/aria' +import * as ariaComponents from '#/components/AriaComponents' import DatalinkInput from '#/components/dashboard/DatalinkInput' import Modal from '#/components/Modal' import ButtonRow from '#/components/styled/ButtonRow' import FocusArea from '#/components/styled/FocusArea' import FocusRing from '#/components/styled/FocusRing' -import UnstyledButton from '#/components/UnstyledButton' import * as jsonSchema from '#/utilities/jsonSchema' @@ -95,16 +95,23 @@ export default function UpsertDatalinkModal(props: UpsertDatalinkModalProps) { - {getText('create')} - - + + {getText('cancel')} - + diff --git a/app/ide-desktop/lib/dashboard/src/modals/UpsertSecretModal.tsx b/app/ide-desktop/lib/dashboard/src/modals/UpsertSecretModal.tsx index 1c0d5af5aa8d..44c49fc0ec5b 100644 --- a/app/ide-desktop/lib/dashboard/src/modals/UpsertSecretModal.tsx +++ b/app/ide-desktop/lib/dashboard/src/modals/UpsertSecretModal.tsx @@ -10,12 +10,12 @@ import * as modalProvider from '#/providers/ModalProvider' import * as textProvider from '#/providers/TextProvider' import * as aria from '#/components/aria' +import * as ariaComponents from '#/components/AriaComponents' import Modal from '#/components/Modal' import Button from '#/components/styled/Button' import ButtonRow from '#/components/styled/ButtonRow' import FocusArea from '#/components/styled/FocusArea' import FocusRing from '#/components/styled/FocusRing' -import UnstyledButton from '#/components/UnstyledButton' import type * as backend from '#/services/Backend' @@ -123,16 +123,23 @@ export default function UpsertSecretModal(props: UpsertSecretModalProps) { - {isCreatingSecret ? getText('create') : getText('update')} - - + + {getText('cancel')} - + diff --git a/app/ide-desktop/lib/dashboard/src/pages/authentication/Login.tsx b/app/ide-desktop/lib/dashboard/src/pages/authentication/Login.tsx index 334f77b47994..f38706b9546a 100644 --- a/app/ide-desktop/lib/dashboard/src/pages/authentication/Login.tsx +++ b/app/ide-desktop/lib/dashboard/src/pages/authentication/Login.tsx @@ -18,12 +18,12 @@ import * as textProvider from '#/providers/TextProvider' import AuthenticationPage from '#/pages/authentication/AuthenticationPage' +import * as ariaComponents from '#/components/AriaComponents' import FontAwesomeIcon from '#/components/FontAwesomeIcon' import Input from '#/components/Input' import Link from '#/components/Link' import SubmitButton from '#/components/SubmitButton' import TextLink from '#/components/TextLink' -import UnstyledButton from '#/components/UnstyledButton' import * as eventModule from '#/utilities/event' @@ -66,7 +66,9 @@ export default function Login() { } >
- { shouldReportValidityRef.current = false void signInWithGoogle() @@ -75,8 +77,10 @@ export default function Login() { > {getText('signUpOrLoginWithGoogle')} - - + { shouldReportValidityRef.current = false void signInWithGitHub() @@ -85,7 +89,7 @@ export default function Login() { > {getText('signUpOrLoginWithGitHub')} - +
Date: Wed, 29 May 2024 14:55:08 +1000 Subject: [PATCH 37/55] Minor style fixes --- .../src/components/dashboard/PermissionSelector.tsx | 2 -- .../dashboard/columnHeading/ModifiedColumnHeading.tsx | 2 +- .../dashboard/columnHeading/NameColumnHeading.tsx | 2 +- .../src/layouts/Settings/ActivityLogSettingsTab.tsx | 8 ++++---- .../src/layouts/Settings/KeyboardShortcutsTable.tsx | 2 +- .../src/layouts/Settings/MembersSettingsTabBar.tsx | 2 +- 6 files changed, 8 insertions(+), 10 deletions(-) diff --git a/app/ide-desktop/lib/dashboard/src/components/dashboard/PermissionSelector.tsx b/app/ide-desktop/lib/dashboard/src/components/dashboard/PermissionSelector.tsx index 58e109c03e1f..c185dcd03f83 100644 --- a/app/ide-desktop/lib/dashboard/src/components/dashboard/PermissionSelector.tsx +++ b/app/ide-desktop/lib/dashboard/src/components/dashboard/PermissionSelector.tsx @@ -158,7 +158,6 @@ export default function PermissionSelector(props: PermissionSelectorProps) { size="custom" variant="custom" isDisabled={isDisabled} - focusRingPlacement="after" {...(isDisabled && error != null ? { title: error } : {})} className="relative h-text grow after:absolute after:inset" onPress={() => { @@ -185,7 +184,6 @@ export default function PermissionSelector(props: PermissionSelectorProps) { size="custom" variant="custom" isDisabled={isDisabled} - focusRingPlacement="after" {...(isDisabled && error != null ? { title: error } : {})} className="relative h-text grow rounded-r-full after:absolute after:inset after:rounded-r-full" onPress={() => { diff --git a/app/ide-desktop/lib/dashboard/src/components/dashboard/columnHeading/ModifiedColumnHeading.tsx b/app/ide-desktop/lib/dashboard/src/components/dashboard/columnHeading/ModifiedColumnHeading.tsx index 3085447735c6..c126aade67ce 100644 --- a/app/ide-desktop/lib/dashboard/src/components/dashboard/columnHeading/ModifiedColumnHeading.tsx +++ b/app/ide-desktop/lib/dashboard/src/components/dashboard/columnHeading/ModifiedColumnHeading.tsx @@ -49,7 +49,7 @@ export default function ModifiedColumnHeading( { const nextDirection = isSortActive ? sorting.nextSortDirection(sortInfo.direction) diff --git a/app/ide-desktop/lib/dashboard/src/components/dashboard/columnHeading/NameColumnHeading.tsx b/app/ide-desktop/lib/dashboard/src/components/dashboard/columnHeading/NameColumnHeading.tsx index dcac5d243dd8..36ece95a7be5 100644 --- a/app/ide-desktop/lib/dashboard/src/components/dashboard/columnHeading/NameColumnHeading.tsx +++ b/app/ide-desktop/lib/dashboard/src/components/dashboard/columnHeading/NameColumnHeading.tsx @@ -35,7 +35,7 @@ export default function NameColumnHeading( ? getText('stopSortingByName') : getText('sortByNameDescending') } - className="group flex h-drive-table-heading w-full items-center gap-icon-with-text px-name-column-x" + className="group flex h-drive-table-heading w-full items-center justify-start gap-icon-with-text px-name-column-x" onPress={() => { const nextDirection = isSortActive ? sorting.nextSortDirection(sortInfo.direction) diff --git a/app/ide-desktop/lib/dashboard/src/layouts/Settings/ActivityLogSettingsTab.tsx b/app/ide-desktop/lib/dashboard/src/layouts/Settings/ActivityLogSettingsTab.tsx index 39ea2225efea..b99d8756339e 100644 --- a/app/ide-desktop/lib/dashboard/src/layouts/Settings/ActivityLogSettingsTab.tsx +++ b/app/ide-desktop/lib/dashboard/src/layouts/Settings/ActivityLogSettingsTab.tsx @@ -207,7 +207,7 @@ export default function ActivityLogSettingsTab(props: ActivityLogSettingsTabProp ? getText('stopSortingByName') : getText('sortByNameDescending') } - className="group flex h-drive-table-heading w-full items-center gap-icon-with-text px-name-column-x" + className="group flex h-drive-table-heading w-full items-center justify-start gap-icon-with-text" onPress={() => { const nextDirection = sortInfo?.field === ActivityLogSortableColumn.type @@ -254,7 +254,7 @@ export default function ActivityLogSettingsTab(props: ActivityLogSettingsTabProp ? getText('stopSortingByEmail') : getText('sortByEmailDescending') } - className="group flex h-drive-table-heading w-full items-center gap-icon-with-text px-name-column-x" + className="group flex h-drive-table-heading w-full items-center justify-start gap-icon-with-text" onPress={() => { const nextDirection = sortInfo?.field === ActivityLogSortableColumn.email @@ -301,7 +301,7 @@ export default function ActivityLogSettingsTab(props: ActivityLogSettingsTabProp ? getText('stopSortingByTimestamp') : getText('sortByTimestampDescending') } - className="group flex h-drive-table-heading w-full items-center gap-icon-with-text px-name-column-x" + className="group flex h-drive-table-heading w-full items-center justify-start gap-icon-with-text" onPress={() => { const nextDirection = sortInfo?.field === ActivityLogSortableColumn.timestamp @@ -353,7 +353,7 @@ export default function ActivityLogSettingsTab(props: ActivityLogSettingsTabProp ) : ( sortedLogs.map((log, i) => ( - +
diff --git a/app/ide-desktop/lib/dashboard/src/layouts/Settings/KeyboardShortcutsTable.tsx b/app/ide-desktop/lib/dashboard/src/layouts/Settings/KeyboardShortcutsTable.tsx index 097553ff7ab0..7ead34dc51e8 100644 --- a/app/ide-desktop/lib/dashboard/src/layouts/Settings/KeyboardShortcutsTable.tsx +++ b/app/ide-desktop/lib/dashboard/src/layouts/Settings/KeyboardShortcutsTable.tsx @@ -85,7 +85,7 @@ export default function KeyboardShortcutsTable(props: KeyboardShortcutsTableProp {visibleBindings.map(kv => { const [action, info] = kv return ( - + - + {getText('inviteMembers')} From 613680ce538bcc8cc3f49796cfcc5437b3b2bdf5 Mon Sep 17 00:00:00 2001 From: somebody1234 Date: Wed, 29 May 2024 15:19:24 +1000 Subject: [PATCH 38/55] Key backend queries and mutations on the actual backend object itself --- app/ide-desktop/lib/dashboard/src/App.tsx | 3 +- .../AriaComponents/Button/Button.tsx | 4 +-- .../components/AriaComponents/Button/index.ts | 6 +--- .../lib/dashboard/src/hooks/backendHooks.ts | 29 ++++++++++--------- .../src/modals/NewUserGroupModal.tsx | 13 ++------- 5 files changed, 23 insertions(+), 32 deletions(-) diff --git a/app/ide-desktop/lib/dashboard/src/App.tsx b/app/ide-desktop/lib/dashboard/src/App.tsx index d8ae996b9832..0861d7e39921 100644 --- a/app/ide-desktop/lib/dashboard/src/App.tsx +++ b/app/ide-desktop/lib/dashboard/src/App.tsx @@ -222,7 +222,6 @@ export interface AppRouterProps extends AppProps { function AppRouter(props: AppRouterProps) { const { logger, isAuthenticationDisabled, shouldShowDashboard } = props const { onAuthenticated, projectManagerUrl, projectManagerRootDirectory } = props - backendHooks.useObserveBackend() // `navigateHooks.useNavigate` cannot be used here as it relies on `AuthProvider`, which has not // yet been initialized at this point. // eslint-disable-next-line no-restricted-properties @@ -236,6 +235,8 @@ function AppRouter(props: AppRouterProps) { ? new LocalBackend(projectManagerUrl, projectManagerRootDirectory) : null ) + backendHooks.useObserveBackend(remoteBackend) + backendHooks.useObserveBackend(localBackend) if (detect.IS_DEV_MODE) { // @ts-expect-error This is used exclusively for debugging. window.navigate = navigate diff --git a/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Button/Button.tsx b/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Button/Button.tsx index eb7e5ba65db6..7488cc5dc53e 100644 --- a/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Button/Button.tsx +++ b/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Button/Button.tsx @@ -78,8 +78,8 @@ export const BUTTON_STYLES = twv.tv({ defaultVariants: { loading: false, fullWidth: false, - size: 'xsmall', - rounded: 'large', + size: 'small', + rounded: 'full', variant: 'primary', iconPosition: 'start', showIconOnHover: false, diff --git a/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Button/index.ts b/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Button/index.ts index 84aab4f163a5..3598fa4f2902 100644 --- a/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Button/index.ts +++ b/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Button/index.ts @@ -1,8 +1,4 @@ -/** - * @file - * - * Barrel export file for Button component. - */ +/** @file Barrel export file for Button component. */ export * from './Button' export * from './ButtonGroup' export * from './CopyButton' diff --git a/app/ide-desktop/lib/dashboard/src/hooks/backendHooks.ts b/app/ide-desktop/lib/dashboard/src/hooks/backendHooks.ts index 4f3aa4b8621d..da9f0748aa81 100644 --- a/app/ide-desktop/lib/dashboard/src/hooks/backendHooks.ts +++ b/app/ide-desktop/lib/dashboard/src/hooks/backendHooks.ts @@ -11,7 +11,7 @@ import * as backendModule from '#/services/Backend' import * as uniqueString from '#/utilities/uniqueString' -// FIXME: key queries on `backend`, not `'backend'` so that there is a separate set of queries +// FIXME: key queries on `backend`, not `backend` so that there is a separate set of queries // for each backend // ========================= @@ -19,8 +19,8 @@ import * as uniqueString from '#/utilities/uniqueString' // ========================= /** Listen to all mutations and update state as appropriate when they succeed. - * MUST be unconditionally called exactly once. */ -export function useObserveBackend() { + * MUST be unconditionally called exactly once for each backend type. */ +export function useObserveBackend(backend: Backend | null) { const queryClient = reactQuery.useQueryClient() const [seen] = React.useState(new WeakSet()) const useObserveMutations = ( @@ -37,7 +37,7 @@ export function useObserveBackend() { Parameters unknown>> >({ // Errored mutations can be safely ignored as they should not change the state. - filters: { mutationKey: ['backend', method], status: 'success' }, + filters: { mutationKey: [backend, method], status: 'success' }, // eslint-disable-next-line no-restricted-syntax select: mutation => mutation.state as never, }) @@ -58,7 +58,7 @@ export function useObserveBackend() { ) => { queryClient.setQueryData< Awaited unknown>>> - >(['backend', method], data => (data == null ? data : updater(data))) + >([backend, method], data => (data == null ? data : updater(data))) } useObserveMutations('createUserGroup', state => { setQueryData('listUserGroups', userGroups => @@ -143,7 +143,7 @@ export function useBackendQuery( readonly unknown[] >({ ...options, - queryKey: ['backend', method, ...args, ...(options?.queryKey ?? [])], + queryKey: [backend, method, ...args, ...(options?.queryKey ?? [])], // eslint-disable-next-line no-restricted-syntax, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return queryFn: () => (backend?.[method] as any)?.(...args), }) @@ -174,7 +174,7 @@ export function useBackendMutation( unknown >({ ...options, - mutationKey: ['backend', method, ...(options?.mutationKey ?? [])], + mutationKey: [backend, method, ...(options?.mutationKey ?? [])], // eslint-disable-next-line no-restricted-syntax, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return mutationFn: args => (backend[method] as any)(...args), }) @@ -186,6 +186,7 @@ export function useBackendMutation( /** Access mutation variables from a React Query Mutation. */ export function useBackendMutationVariables( + backend: Backend | null, method: Method, mutationKey?: readonly unknown[] ) { @@ -193,7 +194,7 @@ export function useBackendMutationVariables( Parameters unknown>> >({ filters: { - mutationKey: ['backend', method, ...(mutationKey ?? [])], + mutationKey: [backend, method, ...(mutationKey ?? [])], status: 'pending', }, // eslint-disable-next-line no-restricted-syntax @@ -224,7 +225,7 @@ export function useBackendMutationWithVariables( mutation, mutate: mutation.mutate.bind(mutation), mutateAsync: mutation.mutateAsync.bind(mutation), - variables: useBackendMutationVariables(method, options?.mutationKey), + variables: useBackendMutationVariables(backend, method, options?.mutationKey), } } @@ -262,7 +263,7 @@ export function useBackendListUsers( backend: Backend ): readonly WithPlaceholder[] | null { const listUsersQuery = useBackendQuery(backend, 'listUsers', []) - const changeUserGroupVariables = useBackendMutationVariables('changeUserGroup') + const changeUserGroupVariables = useBackendMutationVariables(backend, 'changeUserGroup') const users = React.useMemo(() => { if (listUsersQuery.data == null) { return null @@ -293,8 +294,8 @@ export function useBackendListUserGroups( const { user } = authProvider.useNonPartialUserSession() invariant(user != null, 'User must exist for user groups to be listed.') const listUserGroupsQuery = useBackendQuery(backend, 'listUserGroups', []) - const createUserGroupVariables = useBackendMutationVariables('createUserGroup') - const deleteUserGroupVariables = useBackendMutationVariables('deleteUserGroup') + const createUserGroupVariables = useBackendMutationVariables(backend, 'createUserGroup') + const deleteUserGroupVariables = useBackendMutationVariables(backend, 'deleteUserGroup') const userGroups = React.useMemo(() => { if (listUserGroupsQuery.data == null) { return null @@ -374,8 +375,8 @@ export function useBackendListTags( backend: Backend | null ): readonly WithPlaceholder[] | null { const listTagsQuery = useBackendQuery(backend, 'listTags', []) - const createTagVariables = useBackendMutationVariables('createTag') - const deleteTagVariables = useBackendMutationVariables('deleteTag') + const createTagVariables = useBackendMutationVariables(backend, 'createTag') + const deleteTagVariables = useBackendMutationVariables(backend, 'deleteTag') const tags = React.useMemo(() => { if (listTagsQuery.data == null) { return null diff --git a/app/ide-desktop/lib/dashboard/src/modals/NewUserGroupModal.tsx b/app/ide-desktop/lib/dashboard/src/modals/NewUserGroupModal.tsx index a7b6147e338c..024272cfab62 100644 --- a/app/ide-desktop/lib/dashboard/src/modals/NewUserGroupModal.tsx +++ b/app/ide-desktop/lib/dashboard/src/modals/NewUserGroupModal.tsx @@ -74,7 +74,7 @@ export default function NewUserGroupModal(props: NewUserGroupModalProps) { { if (event.key !== 'Escape') { @@ -111,20 +111,13 @@ export default function NewUserGroupModal(props: NewUserGroupModalProps) { {getText('create')} - + {getText('cancel')} From 1e0c595b66f796507c933df77bcf92bb4df9a9fd Mon Sep 17 00:00:00 2001 From: somebody1234 Date: Wed, 29 May 2024 15:51:31 +1000 Subject: [PATCH 39/55] Replace `setOrganization` in settings with React Query mutations --- .../lib/dashboard/src/hooks/backendHooks.ts | 79 ++++++++++++++++--- .../lib/dashboard/src/layouts/Settings.tsx | 40 ++-------- .../KeyboardShortcutsSettingsTabBar.tsx | 1 - ...anizationProfilePictureSettingsSection.tsx | 14 +--- .../Settings/OrganizationSettingsSection.tsx | 64 +++++---------- .../Settings/OrganizationSettingsTab.tsx | 9 +-- .../Settings/UserAccountSettingsSection.tsx | 13 ++- 7 files changed, 112 insertions(+), 108 deletions(-) diff --git a/app/ide-desktop/lib/dashboard/src/hooks/backendHooks.ts b/app/ide-desktop/lib/dashboard/src/hooks/backendHooks.ts index da9f0748aa81..c49f2a7e7146 100644 --- a/app/ide-desktop/lib/dashboard/src/hooks/backendHooks.ts +++ b/app/ide-desktop/lib/dashboard/src/hooks/backendHooks.ts @@ -11,9 +11,6 @@ import * as backendModule from '#/services/Backend' import * as uniqueString from '#/utilities/uniqueString' -// FIXME: key queries on `backend`, not `backend` so that there is a separate set of queries -// for each backend - // ========================= // === useObserveBackend === // ========================= @@ -60,6 +57,15 @@ export function useObserveBackend(backend: Backend | null) { Awaited unknown>>> >([backend, method], data => (data == null ? data : updater(data))) } + useObserveMutations('uploadUserPicture', state => { + setQueryData('usersMe', user => state.data ?? user) + }) + useObserveMutations('updateOrganization', state => { + setQueryData('getOrganization', organization => state.data ?? organization) + }) + useObserveMutations('uploadOrganizationPicture', state => { + setQueryData('getOrganization', organization => state.data ?? organization) + }) useObserveMutations('createUserGroup', state => { setQueryData('listUserGroups', userGroups => state.data == null ? userGroups : [state.data, ...userGroups] @@ -264,7 +270,7 @@ export function useBackendListUsers( ): readonly WithPlaceholder[] | null { const listUsersQuery = useBackendQuery(backend, 'listUsers', []) const changeUserGroupVariables = useBackendMutationVariables(backend, 'changeUserGroup') - const users = React.useMemo(() => { + return React.useMemo(() => { if (listUsersQuery.data == null) { return null } else { @@ -280,7 +286,6 @@ export function useBackendListUsers( return result } }, [changeUserGroupVariables, listUsersQuery.data]) - return users } // ================================ @@ -296,7 +301,7 @@ export function useBackendListUserGroups( const listUserGroupsQuery = useBackendQuery(backend, 'listUserGroups', []) const createUserGroupVariables = useBackendMutationVariables(backend, 'createUserGroup') const deleteUserGroupVariables = useBackendMutationVariables(backend, 'deleteUserGroup') - const userGroups = React.useMemo(() => { + return React.useMemo(() => { if (listUserGroupsQuery.data == null) { return null } else { @@ -320,7 +325,6 @@ export function useBackendListUserGroups( deleteUserGroupVariables, listUserGroupsQuery.data, ]) - return userGroups } // ========================================= @@ -341,7 +345,7 @@ export function useBackendListUserGroupsWithUsers( const listUsersQuery = useBackendQuery(backend, 'listUsers', []) // Current user list, including optimistic updates const users = useBackendListUsers(backend) - const userGroups = React.useMemo(() => { + return React.useMemo(() => { if (userGroupsRaw == null || listUsersQuery.data == null || users == null) { return null } else { @@ -363,7 +367,6 @@ export function useBackendListUserGroupsWithUsers( return result } }, [listUsersQuery.data, userGroupsRaw, users]) - return userGroups } // ========================== @@ -377,7 +380,7 @@ export function useBackendListTags( const listTagsQuery = useBackendQuery(backend, 'listTags', []) const createTagVariables = useBackendMutationVariables(backend, 'createTag') const deleteTagVariables = useBackendMutationVariables(backend, 'deleteTag') - const tags = React.useMemo(() => { + return React.useMemo(() => { if (listTagsQuery.data == null) { return null } else { @@ -396,5 +399,59 @@ export function useBackendListTags( ] } }, [createTagVariables, deleteTagVariables, listTagsQuery.data]) - return tags +} + +// ========================= +// === useBackendUsersMe === +// ========================= + +/** The current user, taking into account optimistic state. */ +export function useBackendUsersMe(backend: Backend | null) { + const { user } = authProvider.useNonPartialUserSession() + const updateUserVariables = useBackendMutationVariables(backend, 'updateUser') + const uploadUserPictureVariables = useBackendMutationVariables(backend, 'uploadUserPicture') + return React.useMemo(() => { + if (user == null) { + return null + } else { + let result = user + for (const [{ username }] of updateUserVariables) { + if (username != null) { + result = { ...result, name: username } + } + } + for (const [, file] of uploadUserPictureVariables) { + result = { ...result, profilePicture: backendModule.HttpsUrl(URL.createObjectURL(file)) } + } + return result + } + }, [user, updateUserVariables, uploadUserPictureVariables]) +} + +// ================================= +// === useBackendGetOrganization === +// ================================= + +/** The current user's organization, taking into account optimistic state. */ +export function useBackendGetOrganization(backend: Backend | null) { + const getOrganizationQuery = useBackendQuery(backend, 'getOrganization', []) + const updateOrganizationVariables = useBackendMutationVariables(backend, 'updateOrganization') + const uploadOrganizationPictureVariables = useBackendMutationVariables( + backend, + 'uploadOrganizationPicture' + ) + return React.useMemo(() => { + if (getOrganizationQuery.data == null) { + return null + } else { + let result = getOrganizationQuery.data + for (const [update] of updateOrganizationVariables) { + result = { ...result, ...update } + } + for (const [, file] of uploadOrganizationPictureVariables) { + result = { ...result, picture: backendModule.HttpsUrl(URL.createObjectURL(file)) } + } + return result + } + }, [getOrganizationQuery, updateOrganizationVariables, uploadOrganizationPictureVariables]) } diff --git a/app/ide-desktop/lib/dashboard/src/layouts/Settings.tsx b/app/ide-desktop/lib/dashboard/src/layouts/Settings.tsx index 94d6e8d9c3af..cc46b1e59aed 100644 --- a/app/ide-desktop/lib/dashboard/src/layouts/Settings.tsx +++ b/app/ide-desktop/lib/dashboard/src/layouts/Settings.tsx @@ -3,9 +3,9 @@ import * as React from 'react' import BurgerMenuIcon from 'enso-assets/burger_menu.svg' +import * as backendHooks from '#/hooks/backendHooks' import * as searchParamsState from '#/hooks/searchParamsStateHooks' -import * as authProvider from '#/providers/AuthProvider' import * as textProvider from '#/providers/TextProvider' import AccountSettingsTab from '#/layouts/Settings/AccountSettingsTab' @@ -23,7 +23,6 @@ import * as loader from '#/components/Loader' import * as portal from '#/components/Portal' import Button from '#/components/styled/Button' -import * as backendModule from '#/services/Backend' import type Backend from '#/services/Backend' import * as array from '#/utilities/array' @@ -45,32 +44,12 @@ export default function Settings(props: SettingsProps) { SettingsTab.account, array.includesPredicate(Object.values(SettingsTab)) ) - - const { type: sessionType, user } = authProvider.useNonPartialUserSession() const { getText } = textProvider.useText() const root = portal.useStrictPortalContext() - const [isUserInOrganization, setIsUserInOrganization] = React.useState(true) const [isSidebarPopoverOpen, setIsSidebarPopoverOpen] = React.useState(false) - const [organization, setOrganization] = React.useState(() => ({ - id: user?.organizationId ?? backendModule.OrganizationId(''), - name: null, - email: null, - website: null, - address: null, - picture: null, - })) - - React.useEffect(() => { - void (async () => { - if (sessionType === authProvider.UserSessionType.full) { - const newOrganization = await backend?.getOrganization() - setIsUserInOrganization(newOrganization != null) - if (newOrganization != null) { - setOrganization(newOrganization) - } - } - })() - }, [sessionType, backend]) + const user = backendHooks.useBackendUsersMe(backend) + const organization = backendHooks.useBackendGetOrganization(backend) + const isUserInOrganization = organization != null let content: React.JSX.Element | null switch (settingsTab) { @@ -79,14 +58,7 @@ export default function Settings(props: SettingsProps) { break } case SettingsTab.organization: { - content = - backend == null ? null : ( - - ) + content = backend == null ? null : break } case SettingsTab.members: { @@ -146,7 +118,7 @@ export default function Settings(props: SettingsProps) { settingsTab !== SettingsTab.members && settingsTab !== SettingsTab.userGroups ? user?.name ?? 'your account' - : organization.name ?? 'your organization'} + : organization?.name ?? 'your organization'}
diff --git a/app/ide-desktop/lib/dashboard/src/layouts/Settings/KeyboardShortcutsSettingsTabBar.tsx b/app/ide-desktop/lib/dashboard/src/layouts/Settings/KeyboardShortcutsSettingsTabBar.tsx index 62b1e335d9f4..ff33dffb859c 100644 --- a/app/ide-desktop/lib/dashboard/src/layouts/Settings/KeyboardShortcutsSettingsTabBar.tsx +++ b/app/ide-desktop/lib/dashboard/src/layouts/Settings/KeyboardShortcutsSettingsTabBar.tsx @@ -35,7 +35,6 @@ export default function KeyboardShortcutsSettingsTabBar( { setModal( diff --git a/app/ide-desktop/lib/dashboard/src/layouts/Settings/OrganizationProfilePictureSettingsSection.tsx b/app/ide-desktop/lib/dashboard/src/layouts/Settings/OrganizationProfilePictureSettingsSection.tsx index 752c7df77d82..1873b36e5cce 100644 --- a/app/ide-desktop/lib/dashboard/src/layouts/Settings/OrganizationProfilePictureSettingsSection.tsx +++ b/app/ide-desktop/lib/dashboard/src/layouts/Settings/OrganizationProfilePictureSettingsSection.tsx @@ -12,7 +12,6 @@ import * as aria from '#/components/aria' import FocusRing from '#/components/styled/FocusRing' import SettingsSection from '#/components/styled/settings/SettingsSection' -import type * as backendModule from '#/services/Backend' import type Backend from '#/services/Backend' // ================================================= @@ -22,17 +21,16 @@ import type Backend from '#/services/Backend' /** Props for a {@link OrganizationProfilePictureSettingsSection}. */ export interface OrganizationProfilePictureSettingsSectionProps { readonly backend: Backend - readonly organization: backendModule.OrganizationInfo - readonly setOrganization: React.Dispatch> } /** Settings tab for viewing and editing organization information. */ export default function OrganizationProfilePictureSettingsSection( props: OrganizationProfilePictureSettingsSectionProps ) { - const { backend, organization, setOrganization } = props + const { backend } = props const toastAndLog = toastAndLogHooks.useToastAndLog() const { getText } = textProvider.useText() + const organization = backendHooks.useBackendGetOrganization(backend) const uploadOrganizationPictureMutation = backendHooks.useBackendMutation( backend, @@ -45,11 +43,7 @@ export default function OrganizationProfilePictureSettingsSection( toastAndLog('noNewProfilePictureError') } else { try { - const newOrganization = await uploadOrganizationPictureMutation.mutateAsync([ - { fileName: image.name }, - image, - ]) - setOrganization(newOrganization) + await uploadOrganizationPictureMutation.mutateAsync([{ fileName: image.name }, image]) } catch (error) { toastAndLog(null, error) } @@ -64,7 +58,7 @@ export default function OrganizationProfilePictureSettingsSection( > } /** Settings tab for viewing and editing organization information. */ export default function OrganizationSettingsSection(props: OrganizationSettingsSectionProps) { - const { backend, organization, setOrganization } = props + const { backend } = props const toastAndLog = toastAndLogHooks.useToastAndLog() const { getText } = textProvider.useText() const nameRef = React.useRef(null) const emailRef = React.useRef(null) const websiteRef = React.useRef(null) const locationRef = React.useRef(null) + const organization = backendHooks.useBackendGetOrganization(backend) const updateOrganizationMutation = backendHooks.useBackendMutation(backend, 'updateOrganization') const doUpdateName = async () => { - const oldName = organization.name ?? null + const oldName = organization?.name ?? null const name = nameRef.current?.value ?? '' if (oldName !== name) { try { - setOrganization(object.merger({ name: name })) - const newOrganization = await updateOrganizationMutation.mutateAsync([{ name }]) - if (newOrganization != null) { - setOrganization(newOrganization) - } + await updateOrganizationMutation.mutateAsync([{ name }]) } catch (error) { - setOrganization(object.merger({ name: oldName })) toastAndLog(null, error) const ref = nameRef.current if (ref) { @@ -62,17 +54,12 @@ export default function OrganizationSettingsSection(props: OrganizationSettingsS } const doUpdateEmail = async () => { - const oldEmail = organization.email ?? null + const oldEmail = organization?.email ?? null const email = backendModule.EmailAddress(emailRef.current?.value ?? '') if (oldEmail !== email) { try { - setOrganization(object.merger({ email })) - const newOrganization = await updateOrganizationMutation.mutateAsync([{ email }]) - if (newOrganization != null) { - setOrganization(newOrganization) - } + await updateOrganizationMutation.mutateAsync([{ email }]) } catch (error) { - setOrganization(object.merger({ email: oldEmail })) toastAndLog(null, error) const ref = emailRef.current if (ref) { @@ -83,14 +70,12 @@ export default function OrganizationSettingsSection(props: OrganizationSettingsS } const doUpdateWebsite = async () => { - const oldWebsite = organization.website ?? null + const oldWebsite = organization?.website ?? null const website = backendModule.HttpsUrl(websiteRef.current?.value ?? '') if (oldWebsite !== website) { try { - setOrganization(object.merger({ website })) await updateOrganizationMutation.mutateAsync([{ website }]) } catch (error) { - setOrganization(object.merger({ website: oldWebsite })) toastAndLog(null, error) const ref = websiteRef.current if (ref) { @@ -101,19 +86,12 @@ export default function OrganizationSettingsSection(props: OrganizationSettingsS } const doUpdateLocation = async () => { - const oldLocation = organization.address ?? null + const oldLocation = organization?.address ?? null const location = locationRef.current?.value ?? '' if (oldLocation !== location) { try { - setOrganization(object.merger({ address: location })) - const newOrganization = await updateOrganizationMutation.mutateAsync([ - { address: location }, - ]) - if (newOrganization != null) { - setOrganization(newOrganization) - } + await updateOrganizationMutation.mutateAsync([{ address: location }]) } catch (error) { - setOrganization(object.merger({ address: oldLocation })) toastAndLog(null, error) const ref = locationRef.current if (ref) { @@ -127,30 +105,30 @@ export default function OrganizationSettingsSection(props: OrganizationSettingsS
{getText('organizationDisplayName')} {getText('email')} { @@ -168,23 +146,23 @@ export default function OrganizationSettingsSection(props: OrganizationSettingsS /> {getText('website')} @@ -192,7 +170,7 @@ export default function OrganizationSettingsSection(props: OrganizationSettingsS diff --git a/app/ide-desktop/lib/dashboard/src/layouts/Settings/OrganizationSettingsTab.tsx b/app/ide-desktop/lib/dashboard/src/layouts/Settings/OrganizationSettingsTab.tsx index b43bfe2fadd1..c37d687ad6e8 100644 --- a/app/ide-desktop/lib/dashboard/src/layouts/Settings/OrganizationSettingsTab.tsx +++ b/app/ide-desktop/lib/dashboard/src/layouts/Settings/OrganizationSettingsTab.tsx @@ -4,7 +4,6 @@ import * as React from 'react' import OrganizationProfilePictureSettingsSection from '#/layouts/Settings/OrganizationProfilePictureSettingsSection' import OrganizationSettingsSection from '#/layouts/Settings/OrganizationSettingsSection' -import type * as backendModule from '#/services/Backend' import type Backend from '#/services/Backend' // =============================== @@ -14,18 +13,18 @@ import type Backend from '#/services/Backend' /** Props for a {@link OrganizationSettingsTab}. */ export interface OrganizationSettingsTabProps { readonly backend: Backend - readonly organization: backendModule.OrganizationInfo - readonly setOrganization: React.Dispatch> } /** Settings tab for viewing and editing organization information. */ export default function OrganizationSettingsTab(props: OrganizationSettingsTabProps) { + const { backend } = props + return (
- +
- +
) } diff --git a/app/ide-desktop/lib/dashboard/src/layouts/Settings/UserAccountSettingsSection.tsx b/app/ide-desktop/lib/dashboard/src/layouts/Settings/UserAccountSettingsSection.tsx index 82b9eff8c1be..4f62c7f64602 100644 --- a/app/ide-desktop/lib/dashboard/src/layouts/Settings/UserAccountSettingsSection.tsx +++ b/app/ide-desktop/lib/dashboard/src/layouts/Settings/UserAccountSettingsSection.tsx @@ -27,15 +27,16 @@ export interface UserAccountSettingsSectionProps { /** Settings section for viewing and editing account information. */ export default function UserAccountSettingsSection(props: UserAccountSettingsSectionProps) { const { backend } = props - const toastAndLog = toastAndLogHooks.useToastAndLog() const { setUser } = authProvider.useAuth() - const { user } = authProvider.useNonPartialUserSession() + const toastAndLog = toastAndLogHooks.useToastAndLog() const { getText } = textProvider.useText() + const nameRef = React.useRef(null) + const user = backendHooks.useBackendUsersMe(backend) const updateUserMutation = backendHooks.useBackendMutation(backend, 'updateUser') const doUpdateName = async (newName: string) => { - const oldName = user?.name ?? '' + const oldName = user?.name ?? null if (newName === oldName) { return } else { @@ -44,6 +45,10 @@ export default function UserAccountSettingsSection(props: UserAccountSettingsSec setUser(object.merger({ name: newName })) } catch (error) { toastAndLog(null, error) + const ref = nameRef.current + if (ref) { + ref.value = oldName ?? '' + } } return } @@ -56,7 +61,7 @@ export default function UserAccountSettingsSection(props: UserAccountSettingsSec {getText('name')} - +
From 6d242edca24020c96f321616df0dc06d4a8a6878 Mon Sep 17 00:00:00 2001 From: somebody1234 Date: Wed, 29 May 2024 16:17:52 +1000 Subject: [PATCH 40/55] Fix bugs related to updating organizations and users --- .../lib/dashboard/src/hooks/backendHooks.ts | 83 +++++++++++++++++-- .../ProfilePictureSettingsSection.tsx | 2 +- 2 files changed, 77 insertions(+), 8 deletions(-) diff --git a/app/ide-desktop/lib/dashboard/src/hooks/backendHooks.ts b/app/ide-desktop/lib/dashboard/src/hooks/backendHooks.ts index c49f2a7e7146..c40deb5a4672 100644 --- a/app/ide-desktop/lib/dashboard/src/hooks/backendHooks.ts +++ b/app/ide-desktop/lib/dashboard/src/hooks/backendHooks.ts @@ -11,6 +11,62 @@ import * as backendModule from '#/services/Backend' import * as uniqueString from '#/utilities/uniqueString' +// ============================ +// === revokeUserPictureUrl === +// ============================ + +const USER_PICTURE_URL_REVOKERS = new WeakMap void>() + +/** Create the corresponding "user picture" URL for the given backend. */ +function createUserPictureUrl(backend: Backend | null, picture: Blob) { + if (backend != null) { + USER_PICTURE_URL_REVOKERS.get(backend)?.() + const url = URL.createObjectURL(picture) + USER_PICTURE_URL_REVOKERS.set(backend, () => { + URL.revokeObjectURL(url) + }) + return url + } else { + // This should never happen, so use an arbitrary URL. + return location.href + } +} + +/** Revoke the corresponding "user picture" URL for the given backend. */ +function revokeUserPictureUrl(backend: Backend | null) { + if (backend != null) { + USER_PICTURE_URL_REVOKERS.get(backend)?.() + } +} + +// ==================================== +// === revokeOrganizationPictureUrl === +// ==================================== + +const ORGANIZATION_PICTURE_URL_REVOKERS = new WeakMap void>() + +/** Create the corresponding "organization picture" URL for the given backend. */ +function createOrganizationPictureUrl(backend: Backend | null, picture: Blob) { + if (backend != null) { + ORGANIZATION_PICTURE_URL_REVOKERS.get(backend)?.() + const url = URL.createObjectURL(picture) + ORGANIZATION_PICTURE_URL_REVOKERS.set(backend, () => { + URL.revokeObjectURL(url) + }) + return url + } else { + // This should never happen, so use an arbitrary URL. + return location.href + } +} + +/** Revoke the corresponding "organization picture" URL for the given backend. */ +function revokeOrganizationPictureUrl(backend: Backend | null) { + if (backend != null) { + ORGANIZATION_PICTURE_URL_REVOKERS.get(backend)?.() + } +} + // ========================= // === useObserveBackend === // ========================= @@ -58,12 +114,14 @@ export function useObserveBackend(backend: Backend | null) { >([backend, method], data => (data == null ? data : updater(data))) } useObserveMutations('uploadUserPicture', state => { + revokeUserPictureUrl(backend) setQueryData('usersMe', user => state.data ?? user) }) useObserveMutations('updateOrganization', state => { setQueryData('getOrganization', organization => state.data ?? organization) }) useObserveMutations('uploadOrganizationPicture', state => { + revokeOrganizationPictureUrl(backend) setQueryData('getOrganization', organization => state.data ?? organization) }) useObserveMutations('createUserGroup', state => { @@ -407,25 +465,28 @@ export function useBackendListTags( /** The current user, taking into account optimistic state. */ export function useBackendUsersMe(backend: Backend | null) { - const { user } = authProvider.useNonPartialUserSession() + const usersMeQuery = useBackendQuery(backend, 'usersMe', []) const updateUserVariables = useBackendMutationVariables(backend, 'updateUser') const uploadUserPictureVariables = useBackendMutationVariables(backend, 'uploadUserPicture') return React.useMemo(() => { - if (user == null) { + if (usersMeQuery.data == null) { return null } else { - let result = user + let result = usersMeQuery.data for (const [{ username }] of updateUserVariables) { if (username != null) { result = { ...result, name: username } } } for (const [, file] of uploadUserPictureVariables) { - result = { ...result, profilePicture: backendModule.HttpsUrl(URL.createObjectURL(file)) } + result = { + ...result, + profilePicture: backendModule.HttpsUrl(createUserPictureUrl(backend, file)), + } } return result } - }, [user, updateUserVariables, uploadUserPictureVariables]) + }, [backend, usersMeQuery.data, updateUserVariables, uploadUserPictureVariables]) } // ================================= @@ -449,9 +510,17 @@ export function useBackendGetOrganization(backend: Backend | null) { result = { ...result, ...update } } for (const [, file] of uploadOrganizationPictureVariables) { - result = { ...result, picture: backendModule.HttpsUrl(URL.createObjectURL(file)) } + result = { + ...result, + picture: backendModule.HttpsUrl(createOrganizationPictureUrl(backend, file)), + } } return result } - }, [getOrganizationQuery, updateOrganizationVariables, uploadOrganizationPictureVariables]) + }, [ + backend, + getOrganizationQuery.data, + updateOrganizationVariables, + uploadOrganizationPictureVariables, + ]) } diff --git a/app/ide-desktop/lib/dashboard/src/layouts/Settings/ProfilePictureSettingsSection.tsx b/app/ide-desktop/lib/dashboard/src/layouts/Settings/ProfilePictureSettingsSection.tsx index 6043715261b6..8b5dbcb2633c 100644 --- a/app/ide-desktop/lib/dashboard/src/layouts/Settings/ProfilePictureSettingsSection.tsx +++ b/app/ide-desktop/lib/dashboard/src/layouts/Settings/ProfilePictureSettingsSection.tsx @@ -29,7 +29,7 @@ export default function ProfilePictureSettingsSection(props: ProfilePictureSetti const { backend } = props const toastAndLog = toastAndLogHooks.useToastAndLog() const { setUser } = authProvider.useAuth() - const { user } = authProvider.useNonPartialUserSession() + const user = backendHooks.useBackendUsersMe(backend) const { getText } = textProvider.useText() const uploadUserPictureMutation = backendHooks.useBackendMutation(backend, 'uploadUserPicture') From 63280d50dff3b810ef4ad31adea62bbf27450a60 Mon Sep 17 00:00:00 2001 From: somebody1234 Date: Wed, 29 May 2024 16:36:59 +1000 Subject: [PATCH 41/55] Fix type errors and lint errors --- .../AriaComponents/Button/CloseButton.tsx | 1 + .../src/components/ContextMenuEntry.tsx | 2 +- .../lib/dashboard/src/components/Input.tsx | 3 +- .../lib/dashboard/src/hooks/backendHooks.ts | 2 +- .../lib/dashboard/src/layouts/AssetPanel.tsx | 4 +- .../dashboard/src/layouts/AssetProperties.tsx | 75 ++++++++++++------- .../dashboard/src/layouts/AssetSearchBar.tsx | 11 ++- .../lib/dashboard/src/layouts/Labels.tsx | 2 +- .../Settings/ActivityLogSettingsTab.tsx | 1 - .../src/layouts/Settings/MembersTable.tsx | 10 ++- .../lib/dashboard/src/layouts/TopBar.tsx | 9 ++- .../src/modals/ManagePermissionsModal.tsx | 1 - .../dashboard/src/modals/NewLabelModal.tsx | 9 ++- .../src/pages/dashboard/Dashboard.tsx | 6 +- .../src/providers/BackendProvider.tsx | 2 - 15 files changed, 79 insertions(+), 59 deletions(-) diff --git a/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Button/CloseButton.tsx b/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Button/CloseButton.tsx index 972c079225dc..5c870ca98ac3 100644 --- a/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Button/CloseButton.tsx +++ b/app/ide-desktop/lib/dashboard/src/components/AriaComponents/Button/CloseButton.tsx @@ -37,6 +37,7 @@ export function CloseButton(props: CloseButtonProps) { className={values => tailwindMerge.twMerge( 'h-3 w-3 bg-primary/30 hover:bg-red-500/80 focus-visible:bg-red-500/80 focus-visible:outline-offset-1', + // @ts-expect-error className can be a function or a string typeof className === 'function' ? className(values) : className ) } diff --git a/app/ide-desktop/lib/dashboard/src/components/ContextMenuEntry.tsx b/app/ide-desktop/lib/dashboard/src/components/ContextMenuEntry.tsx index 3164d3679eac..252751fd9ea8 100644 --- a/app/ide-desktop/lib/dashboard/src/components/ContextMenuEntry.tsx +++ b/app/ide-desktop/lib/dashboard/src/components/ContextMenuEntry.tsx @@ -14,5 +14,5 @@ export interface ContextMenuEntryProps /** An item in a menu. */ export default function ContextMenuEntry(props: ContextMenuEntryProps) { - return + return } diff --git a/app/ide-desktop/lib/dashboard/src/components/Input.tsx b/app/ide-desktop/lib/dashboard/src/components/Input.tsx index 8cf952b67cbf..daa1e9d72534 100644 --- a/app/ide-desktop/lib/dashboard/src/components/Input.tsx +++ b/app/ide-desktop/lib/dashboard/src/components/Input.tsx @@ -30,8 +30,7 @@ export default function Input(props: InputProps) { {type === 'password' && allowShowingPassword && ( { setIsShowingPassword(show => !show) }} diff --git a/app/ide-desktop/lib/dashboard/src/hooks/backendHooks.ts b/app/ide-desktop/lib/dashboard/src/hooks/backendHooks.ts index c40deb5a4672..b4e389cf6115 100644 --- a/app/ide-desktop/lib/dashboard/src/hooks/backendHooks.ts +++ b/app/ide-desktop/lib/dashboard/src/hooks/backendHooks.ts @@ -390,7 +390,7 @@ export function useBackendListUserGroups( // ========================================= /** A user group, as well as the users that are a part of the user group. */ -export interface UserGroupInfoWithUsers { +export interface UserGroupInfoWithUsers extends backendModule.UserGroupInfo { readonly users: readonly WithPlaceholder[] } diff --git a/app/ide-desktop/lib/dashboard/src/layouts/AssetPanel.tsx b/app/ide-desktop/lib/dashboard/src/layouts/AssetPanel.tsx index 4813e15828a4..56b4b598b333 100644 --- a/app/ide-desktop/lib/dashboard/src/layouts/AssetPanel.tsx +++ b/app/ide-desktop/lib/dashboard/src/layouts/AssetPanel.tsx @@ -64,13 +64,12 @@ export interface AssetPanelProps extends AssetPanelRequiredProps { readonly isReadonly?: boolean readonly setQuery: React.Dispatch> readonly category: Category - readonly labels: backendModule.Label[] readonly dispatchAssetEvent: (event: assetEvent.AssetEvent) => void } /** A panel containing the description and settings for an asset. */ export default function AssetPanel(props: AssetPanelProps) { - const { backend, item, setItem, setQuery, category, labels, dispatchAssetEvent } = props + const { backend, item, setItem, setQuery, category, dispatchAssetEvent } = props const { isReadonly = false } = props const { getText } = textProvider.useText() @@ -149,7 +148,6 @@ export default function AssetPanel(props: AssetPanelProps) { item={item} setItem={setItem} category={category} - labels={labels} setQuery={setQuery} dispatchAssetEvent={dispatchAssetEvent} /> diff --git a/app/ide-desktop/lib/dashboard/src/layouts/AssetProperties.tsx b/app/ide-desktop/lib/dashboard/src/layouts/AssetProperties.tsx index 5c6e01beaed8..498724128cc4 100644 --- a/app/ide-desktop/lib/dashboard/src/layouts/AssetProperties.tsx +++ b/app/ide-desktop/lib/dashboard/src/layouts/AssetProperties.tsx @@ -1,35 +1,58 @@ +; /** @file Display and modify the properties of an asset. */ -import * as React from 'react' +import * as React from 'react'; -import PenIcon from 'enso-assets/pen.svg' -import * as datalinkValidator from '#/data/datalinkValidator' -import * as backendHooks from '#/hooks/backendHooks' -import * as toastAndLogHooks from '#/hooks/toastAndLogHooks' +import PenIcon from 'enso-assets/pen.svg'; -import * as authProvider from '#/providers/AuthProvider' -import * as textProvider from '#/providers/TextProvider' -import type * as assetEvent from '#/events/assetEvent' -import type Category from '#/layouts/CategorySwitcher/Category' +import * as datalinkValidator from '#/data/datalinkValidator'; + + + +import * as backendHooks from '#/hooks/backendHooks'; +import * as toastAndLogHooks from '#/hooks/toastAndLogHooks'; + + + +import * as authProvider from '#/providers/AuthProvider'; +import * as textProvider from '#/providers/TextProvider'; + + + +import type * as assetEvent from '#/events/assetEvent'; + + + +import type Category from '#/layouts/CategorySwitcher/Category'; + + + +import * as aria from '#/components/aria'; +import * as ariaComponents from '#/components/AriaComponents'; +import SharedWithColumn from '#/components/dashboard/column/SharedWithColumn'; +import DatalinkInput from '#/components/dashboard/DatalinkInput'; +import Label from '#/components/dashboard/Label'; +import StatelessSpinner, * as statelessSpinner from '#/components/StatelessSpinner'; +import Button from '#/components/styled/Button'; + + + +import * as backendModule from '#/services/Backend'; +import type Backend from '#/services/Backend'; + + + +import type AssetQuery from '#/utilities/AssetQuery'; +import type * as assetTreeNode from '#/utilities/AssetTreeNode'; +import * as object from '#/utilities/object'; +import * as permissions from '#/utilities/permissions'; + -import * as aria from '#/components/aria' -import * as ariaComponents from '#/components/AriaComponents' -import SharedWithColumn from '#/components/dashboard/column/SharedWithColumn' -import DatalinkInput from '#/components/dashboard/DatalinkInput' -import Label from '#/components/dashboard/Label' -import StatelessSpinner, * as statelessSpinner from '#/components/StatelessSpinner' -import Button from '#/components/styled/Button' -import * as backendModule from '#/services/Backend' -import type Backend from '#/services/Backend' -import type AssetQuery from '#/utilities/AssetQuery' -import type * as assetTreeNode from '#/utilities/AssetTreeNode' -import * as object from '#/utilities/object' -import * as permissions from '#/utilities/permissions' // ======================= // === AssetProperties === @@ -41,7 +64,6 @@ export interface AssetPropertiesProps { readonly item: assetTreeNode.AnyAssetTreeNode readonly setItem: React.Dispatch> readonly category: Category - readonly labels: backendModule.Label[] readonly setQuery: React.Dispatch> readonly dispatchAssetEvent: (event: assetEvent.AssetEvent) => void readonly isReadonly?: boolean @@ -49,7 +71,7 @@ export interface AssetPropertiesProps { /** Display and modify the properties of an asset. */ export default function AssetProperties(props: AssetPropertiesProps) { - const { backend, item: itemRaw, setItem: setItemRaw, category, labels, setQuery } = props + const { backend, item: itemRaw, setItem: setItemRaw, category, setQuery } = props const { isReadonly = false, dispatchAssetEvent } = props const { user } = authProvider.useNonPartialUserSession() @@ -75,6 +97,7 @@ export default function AssetProperties(props: AssetPropertiesProps) { }, [/* should never change */ setItemRaw] ) + const labels = backendHooks.useBackendListTags(backend) ?? [] const self = item.item.permissions?.find( backendModule.isUserPermissionAnd(permission => permission.user.userId === user?.userId) ) @@ -103,7 +126,7 @@ export default function AssetProperties(props: AssetPropertiesProps) { setIsDatalinkFetched(true) } })() - }, [backend, item.item]) + }, [backend, item.item, getDatalinkMutation]) const doEditDescription = async () => { setIsEditingDescription(false) @@ -313,4 +336,4 @@ export default function AssetProperties(props: AssetPropertiesProps) { )} ) -} +} \ No newline at end of file diff --git a/app/ide-desktop/lib/dashboard/src/layouts/AssetSearchBar.tsx b/app/ide-desktop/lib/dashboard/src/layouts/AssetSearchBar.tsx index eabd4a6a9d15..9506bd3e6c6a 100644 --- a/app/ide-desktop/lib/dashboard/src/layouts/AssetSearchBar.tsx +++ b/app/ide-desktop/lib/dashboard/src/layouts/AssetSearchBar.tsx @@ -6,6 +6,8 @@ import * as tailwindMerge from 'tailwind-merge' import FindIcon from 'enso-assets/find.svg' import * as detect from 'enso-common/src/detect' +import * as backendHooks from '#/hooks/backendHooks' + import * as modalProvider from '#/providers/ModalProvider' import * as textProvider from '#/providers/TextProvider' @@ -14,7 +16,7 @@ import Label from '#/components/dashboard/Label' import FocusArea from '#/components/styled/FocusArea' import FocusRing from '#/components/styled/FocusRing' -import type * as backend from '#/services/Backend' +import type Backend from '#/services/Backend' import * as array from '#/utilities/array' import AssetQuery from '#/utilities/AssetQuery' @@ -110,16 +112,16 @@ function Tags(props: InternalTagsProps) { /** Props for a {@link AssetSearchBar}. */ export interface AssetSearchBarProps { + readonly backend: Backend | null readonly isCloud: boolean readonly query: AssetQuery readonly setQuery: React.Dispatch> - readonly labels: backend.Label[] readonly suggestions: Suggestion[] } /** A search bar containing a text input, and a list of suggestions. */ export default function AssetSearchBar(props: AssetSearchBarProps) { - const { isCloud, query, setQuery, labels, suggestions: rawSuggestions } = props + const { backend, isCloud, query, setQuery, suggestions: rawSuggestions } = props const { getText } = textProvider.useText() const { modalRef } = modalProvider.useModalRef() /** A cached query as of the start of tabbing. */ @@ -135,6 +137,7 @@ export default function AssetSearchBar(props: AssetSearchBarProps) { const querySource = React.useRef(QuerySource.external) const rootRef = React.useRef(null) const searchRef = React.useRef(null) + const labels = backendHooks.useBackendListTags(backend) ?? [] areSuggestionsVisibleRef.current = areSuggestionsVisible React.useEffect(() => { @@ -310,7 +313,7 @@ export default function AssetSearchBar(props: AssetSearchBarProps) { data-testid="asset-search-labels" className="pointer-events-auto flex gap-buttons p-search-suggestions" > - {labels + {[...labels] .sort((a, b) => string.compareCaseInsensitive(a.value, b.value)) .map(label => { const negated = query.negativeLabels.some(term => diff --git a/app/ide-desktop/lib/dashboard/src/layouts/Labels.tsx b/app/ide-desktop/lib/dashboard/src/layouts/Labels.tsx index 22220eb639a4..28ea88ec615b 100644 --- a/app/ide-desktop/lib/dashboard/src/layouts/Labels.tsx +++ b/app/ide-desktop/lib/dashboard/src/layouts/Labels.tsx @@ -20,7 +20,7 @@ import ConfirmDeleteModal from '#/modals/ConfirmDeleteModal' import DragModal from '#/modals/DragModal' import NewLabelModal from '#/modals/NewLabelModal' -import Backend from '#/services/Backend' +import type Backend from '#/services/Backend' import * as array from '#/utilities/array' import type AssetQuery from '#/utilities/AssetQuery' diff --git a/app/ide-desktop/lib/dashboard/src/layouts/Settings/ActivityLogSettingsTab.tsx b/app/ide-desktop/lib/dashboard/src/layouts/Settings/ActivityLogSettingsTab.tsx index b99d8756339e..b87bc14e1802 100644 --- a/app/ide-desktop/lib/dashboard/src/layouts/Settings/ActivityLogSettingsTab.tsx +++ b/app/ide-desktop/lib/dashboard/src/layouts/Settings/ActivityLogSettingsTab.tsx @@ -9,7 +9,6 @@ import Play2Icon from 'enso-assets/play2.svg' import SortAscendingIcon from 'enso-assets/sort_ascending.svg' import TrashIcon from 'enso-assets/trash.svg' -import * as asyncEffectHooks from '#/hooks/asyncEffectHooks' import * as backendHooks from '#/hooks/backendHooks' import * as textProvider from '#/providers/TextProvider' diff --git a/app/ide-desktop/lib/dashboard/src/layouts/Settings/MembersTable.tsx b/app/ide-desktop/lib/dashboard/src/layouts/Settings/MembersTable.tsx index 052005ac4711..36dec8c72c7f 100644 --- a/app/ide-desktop/lib/dashboard/src/layouts/Settings/MembersTable.tsx +++ b/app/ide-desktop/lib/dashboard/src/layouts/Settings/MembersTable.tsx @@ -1,7 +1,6 @@ /** @file A list of members in the organization. */ import * as React from 'react' -import * as reactQuery from '@tanstack/react-query' import * as tailwindMerge from 'tailwind-merge' import * as mimeTypes from '#/data/mimeTypes' @@ -47,9 +46,12 @@ export default function MembersTable(props: MembersTableProps) { () => (user == null ? null : { isPlaceholder: false, ...user }), [user] ) - const users = - backendHooks.useBackendListUsers(backend) ?? - (populateWithSelf && userWithPlaceholder != null ? [userWithPlaceholder] : null) + const users = React.useMemo( + () => + backendHooks.useBackendListUsers(backend) ?? + (populateWithSelf && userWithPlaceholder != null ? [userWithPlaceholder] : null), + [backend, populateWithSelf, userWithPlaceholder] + ) const usersMap = React.useMemo( () => new Map((users ?? []).map(member => [member.userId, member])), [users] diff --git a/app/ide-desktop/lib/dashboard/src/layouts/TopBar.tsx b/app/ide-desktop/lib/dashboard/src/layouts/TopBar.tsx index 9d78fed83a53..4a9b5f41765b 100644 --- a/app/ide-desktop/lib/dashboard/src/layouts/TopBar.tsx +++ b/app/ide-desktop/lib/dashboard/src/layouts/TopBar.tsx @@ -13,6 +13,7 @@ import UserBar from '#/layouts/UserBar' import AssetInfoBar from '#/components/dashboard/AssetInfoBar' import type * as backendModule from '#/services/Backend' +import type Backend from '#/services/Backend' import type AssetQuery from '#/utilities/AssetQuery' @@ -22,6 +23,7 @@ import type AssetQuery from '#/utilities/AssetQuery' /** Props for a {@link TopBar}. */ export interface TopBarProps { + readonly backend: Backend | null readonly isCloud: boolean readonly page: pageSwitcher.Page readonly setPage: (page: pageSwitcher.Page) => void @@ -31,7 +33,6 @@ export interface TopBarProps { readonly setIsHelpChatOpen: (isHelpChatOpen: boolean) => void readonly query: AssetQuery readonly setQuery: React.Dispatch> - readonly labels: backendModule.Label[] readonly suggestions: assetSearchBar.Suggestion[] readonly isAssetPanelVisible: boolean readonly isAssetPanelEnabled: boolean @@ -43,9 +44,9 @@ export interface TopBarProps { /** The {@link TopBarProps.setQuery} parameter is used to communicate with the parent component, * because `searchVal` may change parent component's project list. */ export default function TopBar(props: TopBarProps) { - const { isCloud, page, setPage, projectAsset, setProjectAsset } = props + const { backend, isCloud, page, setPage, projectAsset, setProjectAsset } = props const { isEditorDisabled, setIsHelpChatOpen } = props - const { query, setQuery, labels, suggestions, isAssetPanelEnabled } = props + const { query, setQuery, suggestions, isAssetPanelEnabled } = props const { isAssetPanelVisible, setIsAssetPanelEnabled, doRemoveSelf, onSignOut } = props const remoteBackend = backendProvider.useRemoteBackend() const shouldMakeSpaceForExtendedEditorMenu = page === pageSwitcher.Page.editor @@ -58,10 +59,10 @@ export default function TopBar(props: TopBarProps) { ) : (
diff --git a/app/ide-desktop/lib/dashboard/src/modals/ManagePermissionsModal.tsx b/app/ide-desktop/lib/dashboard/src/modals/ManagePermissionsModal.tsx index 720beaacdc31..b40fb1015eda 100644 --- a/app/ide-desktop/lib/dashboard/src/modals/ManagePermissionsModal.tsx +++ b/app/ide-desktop/lib/dashboard/src/modals/ManagePermissionsModal.tsx @@ -4,7 +4,6 @@ import * as React from 'react' import * as toast from 'react-toastify' import isEmail from 'validator/es/lib/isEmail' -import * as asyncEffectHooks from '#/hooks/asyncEffectHooks' import * as backendHooks from '#/hooks/backendHooks' import * as toastAndLogHooks from '#/hooks/toastAndLogHooks' diff --git a/app/ide-desktop/lib/dashboard/src/modals/NewLabelModal.tsx b/app/ide-desktop/lib/dashboard/src/modals/NewLabelModal.tsx index b4ca0d5d2253..bac1ccb57c7e 100644 --- a/app/ide-desktop/lib/dashboard/src/modals/NewLabelModal.tsx +++ b/app/ide-desktop/lib/dashboard/src/modals/NewLabelModal.tsx @@ -46,7 +46,8 @@ export default function NewLabelModal(props: NewLabelModalProps) { const [value, setValue] = React.useState('') const [color, setColor] = React.useState(null) const position = React.useMemo(() => eventTarget.getBoundingClientRect(), [eventTarget]) - const labels = backendHooks.useBackendListTags(backend) ?? [] + const labelsRaw = backendHooks.useBackendListTags(backend) + const labels = React.useMemo(() => labelsRaw ?? [], [labelsRaw]) const labelNames = React.useMemo( () => new Set(labels.map(label => label.value)), [labels] @@ -56,11 +57,11 @@ export default function NewLabelModal(props: NewLabelModalProps) { const createTagMutation = backendHooks.useBackendMutation(backend, 'createTag') - const doSubmit = () => { + const doSubmit = async () => { if (value !== '') { unsetModal() try { - createTagMutation.mutateAsync([{ value, color: color ?? leastUsedColor }]) + await createTagMutation.mutateAsync([{ value, color: color ?? leastUsedColor }]) } catch (error) { toastAndLog(null, error) } @@ -84,7 +85,7 @@ export default function NewLabelModal(props: NewLabelModalProps) { event.preventDefault() // Consider not calling `onSubmit()` here to make it harder to accidentally // delete an important asset. - doSubmit() + void doSubmit() }} > diff --git a/app/ide-desktop/lib/dashboard/src/pages/dashboard/Dashboard.tsx b/app/ide-desktop/lib/dashboard/src/pages/dashboard/Dashboard.tsx index 3e18c67bc0fd..09b3e59f0974 100644 --- a/app/ide-desktop/lib/dashboard/src/pages/dashboard/Dashboard.tsx +++ b/app/ide-desktop/lib/dashboard/src/pages/dashboard/Dashboard.tsx @@ -142,7 +142,6 @@ export default function Dashboard(props: DashboardProps) { array.includes(Object.values(pageSwitcher.Page), value) ) const [query, setQuery] = React.useState(() => AssetQuery.fromString('')) - const [labels, setLabels] = React.useState([]) const [suggestions, setSuggestions] = React.useState([]) const [projectStartupInfo, setProjectStartupInfo] = React.useState(null) @@ -389,6 +388,7 @@ export default function Dashboard(props: DashboardProps) { }} > diff --git a/app/ide-desktop/lib/dashboard/src/providers/BackendProvider.tsx b/app/ide-desktop/lib/dashboard/src/providers/BackendProvider.tsx index 079f2b7328d6..cff28edbf666 100644 --- a/app/ide-desktop/lib/dashboard/src/providers/BackendProvider.tsx +++ b/app/ide-desktop/lib/dashboard/src/providers/BackendProvider.tsx @@ -6,8 +6,6 @@ import invariant from 'tiny-invariant' import * as common from 'enso-common' -import * as localStorageProvider from '#/providers/LocalStorageProvider' - import * as categoryModule from '#/layouts/CategorySwitcher/Category' import type Category from '#/layouts/CategorySwitcher/Category' From 12c1548a97d6a5448fe156c393ab353da47dda00 Mon Sep 17 00:00:00 2001 From: somebody1234 Date: Fri, 31 May 2024 14:52:38 +1000 Subject: [PATCH 42/55] Fix dropzone styling --- .../dashboard/src/layouts/AssetProperties.tsx | 69 +++++++------------ .../lib/dashboard/src/layouts/AssetsTable.tsx | 22 +++--- 2 files changed, 34 insertions(+), 57 deletions(-) diff --git a/app/ide-desktop/lib/dashboard/src/layouts/AssetProperties.tsx b/app/ide-desktop/lib/dashboard/src/layouts/AssetProperties.tsx index 498724128cc4..451208afc405 100644 --- a/app/ide-desktop/lib/dashboard/src/layouts/AssetProperties.tsx +++ b/app/ide-desktop/lib/dashboard/src/layouts/AssetProperties.tsx @@ -1,58 +1,35 @@ -; /** @file Display and modify the properties of an asset. */ -import * as React from 'react'; +import * as React from 'react' +import PenIcon from 'enso-assets/pen.svg' +import * as datalinkValidator from '#/data/datalinkValidator' -import PenIcon from 'enso-assets/pen.svg'; +import * as backendHooks from '#/hooks/backendHooks' +import * as toastAndLogHooks from '#/hooks/toastAndLogHooks' +import * as authProvider from '#/providers/AuthProvider' +import * as textProvider from '#/providers/TextProvider' +import type * as assetEvent from '#/events/assetEvent' -import * as datalinkValidator from '#/data/datalinkValidator'; - - - -import * as backendHooks from '#/hooks/backendHooks'; -import * as toastAndLogHooks from '#/hooks/toastAndLogHooks'; - - - -import * as authProvider from '#/providers/AuthProvider'; -import * as textProvider from '#/providers/TextProvider'; - - - -import type * as assetEvent from '#/events/assetEvent'; - - - -import type Category from '#/layouts/CategorySwitcher/Category'; - - - -import * as aria from '#/components/aria'; -import * as ariaComponents from '#/components/AriaComponents'; -import SharedWithColumn from '#/components/dashboard/column/SharedWithColumn'; -import DatalinkInput from '#/components/dashboard/DatalinkInput'; -import Label from '#/components/dashboard/Label'; -import StatelessSpinner, * as statelessSpinner from '#/components/StatelessSpinner'; -import Button from '#/components/styled/Button'; - - - -import * as backendModule from '#/services/Backend'; -import type Backend from '#/services/Backend'; - - - -import type AssetQuery from '#/utilities/AssetQuery'; -import type * as assetTreeNode from '#/utilities/AssetTreeNode'; -import * as object from '#/utilities/object'; -import * as permissions from '#/utilities/permissions'; - +import type Category from '#/layouts/CategorySwitcher/Category' +import * as aria from '#/components/aria' +import * as ariaComponents from '#/components/AriaComponents' +import SharedWithColumn from '#/components/dashboard/column/SharedWithColumn' +import DatalinkInput from '#/components/dashboard/DatalinkInput' +import Label from '#/components/dashboard/Label' +import StatelessSpinner, * as statelessSpinner from '#/components/StatelessSpinner' +import Button from '#/components/styled/Button' +import * as backendModule from '#/services/Backend' +import type Backend from '#/services/Backend' +import type AssetQuery from '#/utilities/AssetQuery' +import type * as assetTreeNode from '#/utilities/AssetTreeNode' +import * as object from '#/utilities/object' +import * as permissions from '#/utilities/permissions' // ======================= // === AssetProperties === @@ -336,4 +313,4 @@ export default function AssetProperties(props: AssetPropertiesProps) { )} ) -} \ No newline at end of file +} diff --git a/app/ide-desktop/lib/dashboard/src/layouts/AssetsTable.tsx b/app/ide-desktop/lib/dashboard/src/layouts/AssetsTable.tsx index 8c03ae61d538..e3e651ddb2f7 100644 --- a/app/ide-desktop/lib/dashboard/src/layouts/AssetsTable.tsx +++ b/app/ide-desktop/lib/dashboard/src/layouts/AssetsTable.tsx @@ -33,7 +33,6 @@ import AssetsTableContextMenu from '#/layouts/AssetsTableContextMenu' import Category from '#/layouts/CategorySwitcher/Category' import * as aria from '#/components/aria' -import * as ariaComponents from '#/components/AriaComponents' import type * as assetRow from '#/components/dashboard/AssetRow' import AssetRow from '#/components/dashboard/AssetRow' import * as assetRowUtils from '#/components/dashboard/AssetRow/assetRowUtils' @@ -45,6 +44,7 @@ import SelectionBrush from '#/components/SelectionBrush' import Spinner, * as spinner from '#/components/Spinner' import Button from '#/components/styled/Button' import FocusArea from '#/components/styled/FocusArea' +import FocusRing from '#/components/styled/FocusRing' import SvgMask from '#/components/SvgMask' import DragModal from '#/modals/DragModal' @@ -2490,15 +2490,15 @@ export default function AssetsTable(props: AssetsTableProps) { }) }} > - {}} - > - - {getText('assetsDropzoneDescription')} - + + {}} + > + + {getText('assetsDropzoneDescription')} + +
@@ -2536,7 +2536,7 @@ export default function AssetsTable(props: AssetsTableProps) {
{columnsBarProps => ( From 23990b5cf34c402b0eb138908df366f54853b50c Mon Sep 17 00:00:00 2001 From: somebody1234 Date: Fri, 31 May 2024 17:37:39 +1000 Subject: [PATCH 43/55] Add dropzone --- .../lib/dashboard/src/components/SvgMask.tsx | 2 +- .../src/components/dashboard/AssetRow.tsx | 4 +- .../dashboard/column/LabelsColumn.tsx | 2 +- .../dashboard/column/SharedWithColumn.tsx | 2 +- .../dashboard/column/columnUtils.ts | 2 +- .../columnHeading/ModifiedColumnHeading.tsx | 2 +- .../columnHeading/NameColumnHeading.tsx | 2 +- .../src/components/styled/Checkbox.tsx | 2 +- app/ide-desktop/lib/dashboard/src/index.tsx | 1 - .../lib/dashboard/src/layouts/AssetsTable.tsx | 218 ++++++++++-------- .../lib/dashboard/src/layouts/Labels.tsx | 2 +- .../Settings/ActivityLogSettingsTab.tsx | 6 +- .../src/layouts/Settings/UserGroupRow.tsx | 2 +- .../src/layouts/Settings/UserGroupUserRow.tsx | 2 +- .../src/layouts/Settings/UserRow.tsx | 2 +- .../lib/dashboard/src/text/english.json | 2 + .../lib/dashboard/src/text/index.ts | 1 + .../lib/dashboard/tailwind.config.js | 3 - 18 files changed, 137 insertions(+), 120 deletions(-) diff --git a/app/ide-desktop/lib/dashboard/src/components/SvgMask.tsx b/app/ide-desktop/lib/dashboard/src/components/SvgMask.tsx index a62d274b4d3d..06125246fbea 100644 --- a/app/ide-desktop/lib/dashboard/src/components/SvgMask.tsx +++ b/app/ide-desktop/lib/dashboard/src/components/SvgMask.tsx @@ -52,7 +52,7 @@ export default function SvgMask(props: SvgMaskProps) { className={tailwindMerge.twMerge('inline-block h-max w-max', className)} > {/* This is required for this component to have the right size. */} - {alt} + {alt}
) } diff --git a/app/ide-desktop/lib/dashboard/src/components/dashboard/AssetRow.tsx b/app/ide-desktop/lib/dashboard/src/components/dashboard/AssetRow.tsx index ed710285ca94..5f0169b2677c 100644 --- a/app/ide-desktop/lib/dashboard/src/components/dashboard/AssetRow.tsx +++ b/app/ide-desktop/lib/dashboard/src/components/dashboard/AssetRow.tsx @@ -938,7 +938,7 @@ export default function AssetRow(props: AssetRowProps) { case backendModule.AssetType.specialLoading: { return hidden ? null : ( - +