Skip to content

Commit 3420a05

Browse files
authored
Dashboard improvements 08/26/2024 (#10946)
- Address part of enso-org/cloud-v2#1453 - Many of the other issues are addressed in other PRs. - Username in "Set username" dialog defaults to user email - Hide "Share with" column for Free and Solo plans - Reorder categories in left sidebar - Previous: Cloud, My Files, Recent, Trash, ...Users, ...Teams - New: Cloud, Me (formerly My Files), ...Users, Teams, Recent, Trash - Fix #10968 - Show column toggles on Local category too (previously was explicitly specialcased to only be visible on Cloud categories as a remnant of the old design with the backend switcher at the top) - Added to this PR as this PR already touches the column toggles # Important Notes None
1 parent 3bcc694 commit 3420a05

File tree

7 files changed

+119
-102
lines changed

7 files changed

+119
-102
lines changed

app/dashboard/src/components/dashboard/column/columnUtils.ts

Lines changed: 13 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -40,21 +40,6 @@ export const DEFAULT_ENABLED_COLUMNS: ReadonlySet<Column> = new Set([
4040
Column.labels,
4141
])
4242

43-
/** The list of all possible columns for the local backend, in order. */
44-
export const LOCAL_COLUMNS = Object.freeze([Column.name, Column.modified] as const)
45-
46-
/** The list of all possible columns for the cloud backend, in order. */
47-
// This MUST be `as const`, to generate the `ExtraColumn` type above.
48-
export const CLOUD_COLUMNS = Object.freeze([
49-
Column.name,
50-
Column.modified,
51-
Column.sharedWith,
52-
Column.labels,
53-
Column.accessedByProjects,
54-
Column.accessedData,
55-
Column.docs,
56-
] as const)
57-
5843
export const COLUMN_ICONS: Readonly<Record<Column, string>> = {
5944
/* The file column does not have an icon, however this does not matter as it is not
6045
* collapsible. */
@@ -97,20 +82,17 @@ export const COLUMN_CSS_CLASS: Readonly<Record<Column, string>> = {
9782
// =====================
9883

9984
/** Return the full list of columns given the relevant current state. */
100-
export function getColumnList(
101-
backendType: backend.BackendType,
102-
enabledColumns: ReadonlySet<Column>,
103-
) {
104-
let columns: readonly Column[]
105-
switch (backendType) {
106-
case backend.BackendType.local: {
107-
columns = LOCAL_COLUMNS
108-
break
109-
}
110-
case backend.BackendType.remote: {
111-
columns = CLOUD_COLUMNS
112-
break
113-
}
114-
}
115-
return columns.filter((column) => enabledColumns.has(column))
85+
export function getColumnList(user: backend.User, backendType: backend.BackendType) {
86+
const isCloud = backendType === backend.BackendType.remote
87+
const isEnterprise = user.plan === backend.Plan.enterprise
88+
const columns = [
89+
Column.name,
90+
Column.modified,
91+
isCloud && isEnterprise && Column.sharedWith,
92+
isCloud && Column.labels,
93+
isCloud && Column.accessedByProjects,
94+
isCloud && Column.accessedData,
95+
isCloud && Column.docs,
96+
]
97+
return columns.flatMap((column) => (column !== false ? [column] : []))
11698
}

app/dashboard/src/layouts/AssetPanel.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,13 @@ declare module '#/utilities/LocalStorage' {
3939
/** */
4040
interface LocalStorageData {
4141
readonly assetPanelTab: AssetPanelTab
42+
readonly assetPanelWidth: number
4243
}
4344
}
4445

45-
LocalStorage.registerKey('assetPanelTab', {
46-
schema: z.nativeEnum(AssetPanelTab),
46+
LocalStorage.register({
47+
assetPanelTab: { schema: z.nativeEnum(AssetPanelTab) },
48+
assetPanelWidth: { schema: z.number().int() },
4749
})
4850

4951
// ==================

app/dashboard/src/layouts/AssetsTable.tsx

Lines changed: 45 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ declare module '#/utilities/LocalStorage' {
110110
}
111111

112112
LocalStorage.registerKey('enabledColumns', {
113-
schema: z.enum(columnUtils.CLOUD_COLUMNS).array().readonly(),
113+
schema: z.nativeEnum(columnUtils.Column).array().readonly(),
114114
})
115115

116116
// =================
@@ -335,7 +335,9 @@ export default function AssetsTable(props: AssetsTableProps) {
335335
const didLoadingProjectManagerFail = backendProvider.useDidLoadingProjectManagerFail()
336336
const reconnectToProjectManager = backendProvider.useReconnectToProjectManager()
337337
const [enabledColumns, setEnabledColumns] = React.useState(columnUtils.DEFAULT_ENABLED_COLUMNS)
338-
338+
const hiddenColumns = columnUtils
339+
.getColumnList(user, backend.type)
340+
.filter((column) => !enabledColumns.has(column))
339341
const [sortInfo, setSortInfo] =
340342
React.useState<sorting.SortInfo<columnUtils.SortableColumn> | null>(null)
341343
const driveStore = useDriveStore()
@@ -2282,13 +2284,12 @@ export default function AssetsTable(props: AssetsTableProps) {
22822284
rootRef.current != null &&
22832285
headerRowRef.current != null
22842286
) {
2285-
const hiddenColumnsCount = columnUtils.CLOUD_COLUMNS.length - enabledColumns.size
22862287
const shrinkBy =
2287-
COLUMNS_SELECTOR_BASE_WIDTH_PX + COLUMNS_SELECTOR_ICON_WIDTH_PX * hiddenColumnsCount
2288+
COLUMNS_SELECTOR_BASE_WIDTH_PX + COLUMNS_SELECTOR_ICON_WIDTH_PX * hiddenColumns.length
22882289
const rightOffset = rootRef.current.clientWidth + rootRef.current.scrollLeft - shrinkBy
22892290
headerRowRef.current.style.clipPath = `polygon(0 0, ${rightOffset}px 0, ${rightOffset}px 100%, 0 100%)`
22902291
}
2291-
}, [backend.type, enabledColumns.size])
2292+
}, [backend.type, hiddenColumns.length])
22922293

22932294
const updateClipPathObserver = React.useMemo(
22942295
() => new ResizeObserver(updateClipPath),
@@ -2507,7 +2508,9 @@ export default function AssetsTable(props: AssetsTableProps) {
25072508
setAsset,
25082509
}))
25092510

2510-
const columns = columnUtils.getColumnList(backend.type, enabledColumns)
2511+
const columns = columnUtils
2512+
.getColumnList(user, backend.type)
2513+
.filter((column) => enabledColumns.has(column))
25112514

25122515
const headerRow = (
25132516
<tr ref={headerRowRef} className="sticky top-[1px] text-sm font-semibold">
@@ -2895,47 +2898,43 @@ export default function AssetsTable(props: AssetsTableProps) {
28952898
/>
28962899
)}
28972900
<div className="flex h-max min-h-full w-max min-w-full flex-col">
2898-
{isCloud && (
2899-
<div className="flex-0 sticky top-0 flex h-0 flex-col">
2900-
<div
2901-
data-testid="extra-columns"
2902-
className="sticky right-0 flex self-end px-2 py-3"
2903-
>
2904-
<FocusArea direction="horizontal">
2905-
{(columnsBarProps) => (
2906-
<div
2907-
{...aria.mergeProps<JSX.IntrinsicElements['div']>()(columnsBarProps, {
2908-
className: 'inline-flex gap-icons',
2909-
onFocus: () => {
2910-
setKeyboardSelectedIndex(null)
2911-
},
2912-
})}
2913-
>
2914-
{columnUtils.CLOUD_COLUMNS.filter(
2915-
(column) => !enabledColumns.has(column),
2916-
).map((column) => (
2917-
<Button
2918-
key={column}
2919-
light
2920-
image={columnUtils.COLUMN_ICONS[column]}
2921-
alt={getText(columnUtils.COLUMN_SHOW_TEXT_ID[column])}
2922-
onPress={() => {
2923-
const newExtraColumns = new Set(enabledColumns)
2924-
if (enabledColumns.has(column)) {
2925-
newExtraColumns.delete(column)
2926-
} else {
2927-
newExtraColumns.add(column)
2928-
}
2929-
setEnabledColumns(newExtraColumns)
2930-
}}
2931-
/>
2932-
))}
2933-
</div>
2934-
)}
2935-
</FocusArea>
2936-
</div>
2901+
<div className="flex-0 sticky top-0 flex h-0 flex-col">
2902+
<div
2903+
data-testid="extra-columns"
2904+
className="sticky right-0 flex self-end px-2 py-3"
2905+
>
2906+
<FocusArea direction="horizontal">
2907+
{(columnsBarProps) => (
2908+
<div
2909+
{...aria.mergeProps<JSX.IntrinsicElements['div']>()(columnsBarProps, {
2910+
className: 'inline-flex gap-icons',
2911+
onFocus: () => {
2912+
setKeyboardSelectedIndex(null)
2913+
},
2914+
})}
2915+
>
2916+
{hiddenColumns.map((column) => (
2917+
<Button
2918+
key={column}
2919+
light
2920+
image={columnUtils.COLUMN_ICONS[column]}
2921+
alt={getText(columnUtils.COLUMN_SHOW_TEXT_ID[column])}
2922+
onPress={() => {
2923+
const newExtraColumns = new Set(enabledColumns)
2924+
if (enabledColumns.has(column)) {
2925+
newExtraColumns.delete(column)
2926+
} else {
2927+
newExtraColumns.add(column)
2928+
}
2929+
setEnabledColumns(newExtraColumns)
2930+
}}
2931+
/>
2932+
))}
2933+
</div>
2934+
)}
2935+
</FocusArea>
29372936
</div>
2938-
)}
2937+
</div>
29392938
<div className="flex h-full w-min min-w-full grow flex-col">{table}</div>
29402939
</div>
29412940
</div>

app/dashboard/src/layouts/CategorySwitcher.tsx

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -394,25 +394,6 @@ export default function CategorySwitcher(props: CategorySwitcherProps) {
394394
dropZoneLabel={getText('myFilesCategoryDropZoneLabel')}
395395
/>
396396
)}
397-
<CategorySwitcherItem
398-
{...itemProps}
399-
isNested
400-
category={{ type: 'recent' }}
401-
icon={RecentIcon}
402-
label={getText('recentCategory')}
403-
buttonLabel={getText('recentCategoryButtonLabel')}
404-
dropZoneLabel={getText('recentCategoryDropZoneLabel')}
405-
iconClassName="-ml-0.5"
406-
/>
407-
<CategorySwitcherItem
408-
{...itemProps}
409-
isNested
410-
category={{ type: 'trash' }}
411-
icon={Trash2Icon}
412-
label={getText('trashCategory')}
413-
buttonLabel={getText('trashCategoryButtonLabel')}
414-
dropZoneLabel={getText('trashCategoryDropZoneLabel')}
415-
/>
416397
{usersDirectoryQuery.data?.map((userDirectory) => {
417398
if (userDirectory.type !== backend.AssetType.directory) {
418399
return null
@@ -460,6 +441,25 @@ export default function CategorySwitcher(props: CategorySwitcherProps) {
460441
)
461442
}
462443
})}
444+
<CategorySwitcherItem
445+
{...itemProps}
446+
isNested
447+
category={{ type: 'recent' }}
448+
icon={RecentIcon}
449+
label={getText('recentCategory')}
450+
buttonLabel={getText('recentCategoryButtonLabel')}
451+
dropZoneLabel={getText('recentCategoryDropZoneLabel')}
452+
iconClassName="-ml-0.5"
453+
/>
454+
<CategorySwitcherItem
455+
{...itemProps}
456+
isNested
457+
category={{ type: 'trash' }}
458+
icon={Trash2Icon}
459+
label={getText('trashCategory')}
460+
buttonLabel={getText('trashCategoryButtonLabel')}
461+
dropZoneLabel={getText('trashCategoryDropZoneLabel')}
462+
/>
463463
{localBackend && (
464464
<div className="group flex items-center justify-between self-stretch">
465465
<CategorySwitcherItem

app/dashboard/src/pages/authentication/Setup/Setup.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { DASHBOARD_PATH, LOGIN_PATH } from '#/appUtils'
1616

1717
import { useIsFirstRender } from '#/hooks/mountHooks'
1818

19-
import { useAuth, UserSessionType } from '#/providers/AuthProvider'
19+
import { useAuth, UserSessionType, useUserSession } from '#/providers/AuthProvider'
2020
import { useRemoteBackendStrict } from '#/providers/BackendProvider'
2121
import * as textProvider from '#/providers/TextProvider'
2222

@@ -65,9 +65,11 @@ const BASE_STEPS: Step[] = [
6565
*/
6666
component: function SetUsernameStep({ session, goToNextStep }) {
6767
const { setUsername } = useAuth()
68+
const userSession = useUserSession()
6869
const { getText } = textProvider.useText()
6970

70-
const defaultName = session && 'user' in session ? session.user.name : ''
71+
const defaultName =
72+
session && 'user' in session ? session.user.name : userSession?.email ?? ''
7173

7274
return (
7375
<ariaComponents.Form

app/dashboard/src/utilities/LocalStorage.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,19 @@ import type * as z from 'zod'
44
import * as common from 'enso-common'
55

66
import * as object from '#/utilities/object'
7+
import { IS_DEV_MODE } from 'enso-common/src/detect'
8+
import invariant from 'tiny-invariant'
9+
10+
const KEY_DEFINITION_STACK_TRACES = new Map<string, string>()
11+
12+
/** Whether the source location for `LocalStorage.register(key)` is different to the previous
13+
* known source location. */
14+
function isSourceChanged(key: string) {
15+
const stack = (new Error().stack ?? '').replace(/[?]t=\d+:\d+:\d+/g, '')
16+
const isChanged = stack !== KEY_DEFINITION_STACK_TRACES.get(key)
17+
KEY_DEFINITION_STACK_TRACES.set(key, stack)
18+
return isChanged
19+
}
720

821
// ===============================
922
// === LocalStorageKeyMetadata ===
@@ -74,9 +87,28 @@ export default class LocalStorage {
7487

7588
/** Register runtime behavior associated with a {@link LocalStorageKey}. */
7689
static registerKey<K extends LocalStorageKey>(key: K, metadata: LocalStorageKeyMetadata<K>) {
90+
if (IS_DEV_MODE ? isSourceChanged(key) : true) {
91+
invariant(
92+
!(key in LocalStorage.keyMetadata),
93+
`Local storage key '${key}' has already been registered.`,
94+
)
95+
}
7796
LocalStorage.keyMetadata[key] = metadata
7897
}
7998

99+
/** Register runtime behavior associated with a {@link LocalStorageKey}. */
100+
static register<K extends LocalStorageKey>(metadata: { [K_ in K]: LocalStorageKeyMetadata<K_> }) {
101+
for (const key in metadata) {
102+
if (IS_DEV_MODE ? isSourceChanged(key) : true) {
103+
invariant(
104+
!(key in LocalStorage.keyMetadata),
105+
`Local storage key '${key}' has already been registered.`,
106+
)
107+
}
108+
}
109+
Object.assign(LocalStorage.keyMetadata, metadata)
110+
}
111+
80112
/** Retrieve an entry from the stored data. */
81113
get<K extends LocalStorageKey>(key: K) {
82114
return this.values[key]

app/ide-desktop/common/src/text/english.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -282,14 +282,14 @@
282282
"andOtherProjects": "and $0 other projects",
283283

284284
"cloudCategory": "Cloud",
285-
"myFilesCategory": "My Files",
285+
"myFilesCategory": "Me",
286286
"recentCategory": "Recent",
287287
"trashCategory": "Trash",
288288
"userCategory": "$0",
289289
"teamCategory": "$0",
290290
"localCategory": "Local",
291291
"cloudCategoryButtonLabel": "Cloud",
292-
"myFilesCategoryButtonLabel": "My Files",
292+
"myFilesCategoryButtonLabel": "Me",
293293
"recentCategoryButtonLabel": "Recent",
294294
"trashCategoryButtonLabel": "Trash",
295295
"userCategoryButtonLabel": "$0 (User)",

0 commit comments

Comments
 (0)