diff --git a/app/dashboard/src/components/dashboard/column/columnUtils.ts b/app/dashboard/src/components/dashboard/column/columnUtils.ts index 19738db5c19a..096ed76a3c02 100644 --- a/app/dashboard/src/components/dashboard/column/columnUtils.ts +++ b/app/dashboard/src/components/dashboard/column/columnUtils.ts @@ -40,21 +40,6 @@ export const DEFAULT_ENABLED_COLUMNS: ReadonlySet = new Set([ Column.labels, ]) -/** The list of all possible columns for the local backend, in order. */ -export const LOCAL_COLUMNS = Object.freeze([Column.name, Column.modified] as const) - -/** The list of all possible columns for the cloud backend, in order. */ -// This MUST be `as const`, to generate the `ExtraColumn` type above. -export const CLOUD_COLUMNS = Object.freeze([ - Column.name, - Column.modified, - Column.sharedWith, - Column.labels, - Column.accessedByProjects, - Column.accessedData, - Column.docs, -] as const) - export const COLUMN_ICONS: Readonly> = { /* The file column does not have an icon, however this does not matter as it is not * collapsible. */ @@ -97,20 +82,17 @@ export const COLUMN_CSS_CLASS: Readonly> = { // ===================== /** Return the full list of columns given the relevant current state. */ -export function getColumnList( - backendType: backend.BackendType, - enabledColumns: ReadonlySet, -) { - let columns: readonly Column[] - switch (backendType) { - case backend.BackendType.local: { - columns = LOCAL_COLUMNS - break - } - case backend.BackendType.remote: { - columns = CLOUD_COLUMNS - break - } - } - return columns.filter((column) => enabledColumns.has(column)) +export function getColumnList(user: backend.User, backendType: backend.BackendType) { + const isCloud = backendType === backend.BackendType.remote + const isEnterprise = user.plan === backend.Plan.enterprise + const columns = [ + Column.name, + Column.modified, + isCloud && isEnterprise && Column.sharedWith, + isCloud && Column.labels, + isCloud && Column.accessedByProjects, + isCloud && Column.accessedData, + isCloud && Column.docs, + ] + return columns.flatMap((column) => (column !== false ? [column] : [])) } diff --git a/app/dashboard/src/layouts/AssetPanel.tsx b/app/dashboard/src/layouts/AssetPanel.tsx index c1fd1c5c166a..228b50580638 100644 --- a/app/dashboard/src/layouts/AssetPanel.tsx +++ b/app/dashboard/src/layouts/AssetPanel.tsx @@ -39,11 +39,13 @@ declare module '#/utilities/LocalStorage' { /** */ interface LocalStorageData { readonly assetPanelTab: AssetPanelTab + readonly assetPanelWidth: number } } -LocalStorage.registerKey('assetPanelTab', { - schema: z.nativeEnum(AssetPanelTab), +LocalStorage.register({ + assetPanelTab: { schema: z.nativeEnum(AssetPanelTab) }, + assetPanelWidth: { schema: z.number().int() }, }) // ================== diff --git a/app/dashboard/src/layouts/AssetsTable.tsx b/app/dashboard/src/layouts/AssetsTable.tsx index 5f53124f3b05..a0233db30cf9 100644 --- a/app/dashboard/src/layouts/AssetsTable.tsx +++ b/app/dashboard/src/layouts/AssetsTable.tsx @@ -110,7 +110,7 @@ declare module '#/utilities/LocalStorage' { } LocalStorage.registerKey('enabledColumns', { - schema: z.enum(columnUtils.CLOUD_COLUMNS).array().readonly(), + schema: z.nativeEnum(columnUtils.Column).array().readonly(), }) // ================= @@ -335,7 +335,9 @@ export default function AssetsTable(props: AssetsTableProps) { const didLoadingProjectManagerFail = backendProvider.useDidLoadingProjectManagerFail() const reconnectToProjectManager = backendProvider.useReconnectToProjectManager() const [enabledColumns, setEnabledColumns] = React.useState(columnUtils.DEFAULT_ENABLED_COLUMNS) - + const hiddenColumns = columnUtils + .getColumnList(user, backend.type) + .filter((column) => !enabledColumns.has(column)) const [sortInfo, setSortInfo] = React.useState | null>(null) const driveStore = useDriveStore() @@ -2282,13 +2284,12 @@ export default function AssetsTable(props: AssetsTableProps) { rootRef.current != null && headerRowRef.current != null ) { - const hiddenColumnsCount = columnUtils.CLOUD_COLUMNS.length - enabledColumns.size const shrinkBy = - COLUMNS_SELECTOR_BASE_WIDTH_PX + COLUMNS_SELECTOR_ICON_WIDTH_PX * hiddenColumnsCount + COLUMNS_SELECTOR_BASE_WIDTH_PX + COLUMNS_SELECTOR_ICON_WIDTH_PX * hiddenColumns.length const rightOffset = rootRef.current.clientWidth + rootRef.current.scrollLeft - shrinkBy headerRowRef.current.style.clipPath = `polygon(0 0, ${rightOffset}px 0, ${rightOffset}px 100%, 0 100%)` } - }, [backend.type, enabledColumns.size]) + }, [backend.type, hiddenColumns.length]) const updateClipPathObserver = React.useMemo( () => new ResizeObserver(updateClipPath), @@ -2507,7 +2508,9 @@ export default function AssetsTable(props: AssetsTableProps) { setAsset, })) - const columns = columnUtils.getColumnList(backend.type, enabledColumns) + const columns = columnUtils + .getColumnList(user, backend.type) + .filter((column) => enabledColumns.has(column)) const headerRow = ( @@ -2895,47 +2898,43 @@ export default function AssetsTable(props: AssetsTableProps) { /> )}
- {isCloud && ( -
-
- - {(columnsBarProps) => ( -
()(columnsBarProps, { - className: 'inline-flex gap-icons', - onFocus: () => { - setKeyboardSelectedIndex(null) - }, - })} - > - {columnUtils.CLOUD_COLUMNS.filter( - (column) => !enabledColumns.has(column), - ).map((column) => ( -
- )} -
-
+
+
+ + {(columnsBarProps) => ( +
()(columnsBarProps, { + className: 'inline-flex gap-icons', + onFocus: () => { + setKeyboardSelectedIndex(null) + }, + })} + > + {hiddenColumns.map((column) => ( +
+ )} +
- )} +
{table}
diff --git a/app/dashboard/src/layouts/CategorySwitcher.tsx b/app/dashboard/src/layouts/CategorySwitcher.tsx index b89da88f86a0..4359657c8cdb 100644 --- a/app/dashboard/src/layouts/CategorySwitcher.tsx +++ b/app/dashboard/src/layouts/CategorySwitcher.tsx @@ -394,25 +394,6 @@ export default function CategorySwitcher(props: CategorySwitcherProps) { dropZoneLabel={getText('myFilesCategoryDropZoneLabel')} /> )} - - {usersDirectoryQuery.data?.map((userDirectory) => { if (userDirectory.type !== backend.AssetType.directory) { return null @@ -460,6 +441,25 @@ export default function CategorySwitcher(props: CategorySwitcherProps) { ) } })} + + {localBackend && (
() + +/** Whether the source location for `LocalStorage.register(key)` is different to the previous + * known source location. */ +function isSourceChanged(key: string) { + const stack = (new Error().stack ?? '').replace(/[?]t=\d+:\d+:\d+/g, '') + const isChanged = stack !== KEY_DEFINITION_STACK_TRACES.get(key) + KEY_DEFINITION_STACK_TRACES.set(key, stack) + return isChanged +} // =============================== // === LocalStorageKeyMetadata === @@ -74,9 +87,28 @@ export default class LocalStorage { /** Register runtime behavior associated with a {@link LocalStorageKey}. */ static registerKey(key: K, metadata: LocalStorageKeyMetadata) { + if (IS_DEV_MODE ? isSourceChanged(key) : true) { + invariant( + !(key in LocalStorage.keyMetadata), + `Local storage key '${key}' has already been registered.`, + ) + } LocalStorage.keyMetadata[key] = metadata } + /** Register runtime behavior associated with a {@link LocalStorageKey}. */ + static register(metadata: { [K_ in K]: LocalStorageKeyMetadata }) { + for (const key in metadata) { + if (IS_DEV_MODE ? isSourceChanged(key) : true) { + invariant( + !(key in LocalStorage.keyMetadata), + `Local storage key '${key}' has already been registered.`, + ) + } + } + Object.assign(LocalStorage.keyMetadata, metadata) + } + /** Retrieve an entry from the stored data. */ get(key: K) { return this.values[key] diff --git a/app/ide-desktop/common/src/text/english.json b/app/ide-desktop/common/src/text/english.json index c52dd99ecd10..521ce6ea7cd8 100644 --- a/app/ide-desktop/common/src/text/english.json +++ b/app/ide-desktop/common/src/text/english.json @@ -282,14 +282,14 @@ "andOtherProjects": "and $0 other projects", "cloudCategory": "Cloud", - "myFilesCategory": "My Files", + "myFilesCategory": "Me", "recentCategory": "Recent", "trashCategory": "Trash", "userCategory": "$0", "teamCategory": "$0", "localCategory": "Local", "cloudCategoryButtonLabel": "Cloud", - "myFilesCategoryButtonLabel": "My Files", + "myFilesCategoryButtonLabel": "Me", "recentCategoryButtonLabel": "Recent", "trashCategoryButtonLabel": "Trash", "userCategoryButtonLabel": "$0 (User)",