Skip to content

Commit

Permalink
Dashboard improvements 08/26/2024 (#10946)
Browse files Browse the repository at this point in the history
- 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

(cherry picked from commit 3420a05)
  • Loading branch information
somebody1234 authored and jdunkerley committed Sep 9, 2024
1 parent eb1af53 commit 72b4bde
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 102 deletions.
44 changes: 13 additions & 31 deletions app/dashboard/src/components/dashboard/column/columnUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,6 @@ export const DEFAULT_ENABLED_COLUMNS: ReadonlySet<Column> = 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<Record<Column, string>> = {
/* The file column does not have an icon, however this does not matter as it is not
* collapsible. */
Expand Down Expand Up @@ -97,20 +82,17 @@ export const COLUMN_CSS_CLASS: Readonly<Record<Column, string>> = {
// =====================

/** Return the full list of columns given the relevant current state. */
export function getColumnList(
backendType: backend.BackendType,
enabledColumns: ReadonlySet<Column>,
) {
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] : []))
}
6 changes: 4 additions & 2 deletions app/dashboard/src/layouts/AssetPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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() },
})

// ==================
Expand Down
91 changes: 45 additions & 46 deletions app/dashboard/src/layouts/AssetsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
})

// =================
Expand Down Expand Up @@ -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<sorting.SortInfo<columnUtils.SortableColumn> | null>(null)
const driveStore = useDriveStore()
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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 = (
<tr ref={headerRowRef} className="sticky top-[1px] text-sm font-semibold">
Expand Down Expand Up @@ -2895,47 +2898,43 @@ export default function AssetsTable(props: AssetsTableProps) {
/>
)}
<div className="flex h-max min-h-full w-max min-w-full flex-col">
{isCloud && (
<div className="flex-0 sticky top-0 flex h-0 flex-col">
<div
data-testid="extra-columns"
className="sticky right-0 flex self-end px-2 py-3"
>
<FocusArea direction="horizontal">
{(columnsBarProps) => (
<div
{...aria.mergeProps<JSX.IntrinsicElements['div']>()(columnsBarProps, {
className: 'inline-flex gap-icons',
onFocus: () => {
setKeyboardSelectedIndex(null)
},
})}
>
{columnUtils.CLOUD_COLUMNS.filter(
(column) => !enabledColumns.has(column),
).map((column) => (
<Button
key={column}
light
image={columnUtils.COLUMN_ICONS[column]}
alt={getText(columnUtils.COLUMN_SHOW_TEXT_ID[column])}
onPress={() => {
const newExtraColumns = new Set(enabledColumns)
if (enabledColumns.has(column)) {
newExtraColumns.delete(column)
} else {
newExtraColumns.add(column)
}
setEnabledColumns(newExtraColumns)
}}
/>
))}
</div>
)}
</FocusArea>
</div>
<div className="flex-0 sticky top-0 flex h-0 flex-col">
<div
data-testid="extra-columns"
className="sticky right-0 flex self-end px-2 py-3"
>
<FocusArea direction="horizontal">
{(columnsBarProps) => (
<div
{...aria.mergeProps<JSX.IntrinsicElements['div']>()(columnsBarProps, {
className: 'inline-flex gap-icons',
onFocus: () => {
setKeyboardSelectedIndex(null)
},
})}
>
{hiddenColumns.map((column) => (
<Button
key={column}
light
image={columnUtils.COLUMN_ICONS[column]}
alt={getText(columnUtils.COLUMN_SHOW_TEXT_ID[column])}
onPress={() => {
const newExtraColumns = new Set(enabledColumns)
if (enabledColumns.has(column)) {
newExtraColumns.delete(column)
} else {
newExtraColumns.add(column)
}
setEnabledColumns(newExtraColumns)
}}
/>
))}
</div>
)}
</FocusArea>
</div>
)}
</div>
<div className="flex h-full w-min min-w-full grow flex-col">{table}</div>
</div>
</div>
Expand Down
38 changes: 19 additions & 19 deletions app/dashboard/src/layouts/CategorySwitcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -394,25 +394,6 @@ export default function CategorySwitcher(props: CategorySwitcherProps) {
dropZoneLabel={getText('myFilesCategoryDropZoneLabel')}
/>
)}
<CategorySwitcherItem
{...itemProps}
isNested
category={{ type: 'recent' }}
icon={RecentIcon}
label={getText('recentCategory')}
buttonLabel={getText('recentCategoryButtonLabel')}
dropZoneLabel={getText('recentCategoryDropZoneLabel')}
iconClassName="-ml-0.5"
/>
<CategorySwitcherItem
{...itemProps}
isNested
category={{ type: 'trash' }}
icon={Trash2Icon}
label={getText('trashCategory')}
buttonLabel={getText('trashCategoryButtonLabel')}
dropZoneLabel={getText('trashCategoryDropZoneLabel')}
/>
{usersDirectoryQuery.data?.map((userDirectory) => {
if (userDirectory.type !== backend.AssetType.directory) {
return null
Expand Down Expand Up @@ -460,6 +441,25 @@ export default function CategorySwitcher(props: CategorySwitcherProps) {
)
}
})}
<CategorySwitcherItem
{...itemProps}
isNested
category={{ type: 'recent' }}
icon={RecentIcon}
label={getText('recentCategory')}
buttonLabel={getText('recentCategoryButtonLabel')}
dropZoneLabel={getText('recentCategoryDropZoneLabel')}
iconClassName="-ml-0.5"
/>
<CategorySwitcherItem
{...itemProps}
isNested
category={{ type: 'trash' }}
icon={Trash2Icon}
label={getText('trashCategory')}
buttonLabel={getText('trashCategoryButtonLabel')}
dropZoneLabel={getText('trashCategoryDropZoneLabel')}
/>
{localBackend && (
<div className="group flex items-center justify-between self-stretch">
<CategorySwitcherItem
Expand Down
6 changes: 4 additions & 2 deletions app/dashboard/src/pages/authentication/Setup/Setup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { DASHBOARD_PATH, LOGIN_PATH } from '#/appUtils'

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

import { useAuth, UserSessionType } from '#/providers/AuthProvider'
import { useAuth, UserSessionType, useUserSession } from '#/providers/AuthProvider'
import { useRemoteBackendStrict } from '#/providers/BackendProvider'
import * as textProvider from '#/providers/TextProvider'

Expand Down Expand Up @@ -65,9 +65,11 @@ const BASE_STEPS: Step[] = [
*/
component: function SetUsernameStep({ session, goToNextStep }) {
const { setUsername } = useAuth()
const userSession = useUserSession()
const { getText } = textProvider.useText()

const defaultName = session && 'user' in session ? session.user.name : ''
const defaultName =
session && 'user' in session ? session.user.name : userSession?.email ?? ''

return (
<ariaComponents.Form
Expand Down
32 changes: 32 additions & 0 deletions app/dashboard/src/utilities/LocalStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@ import type * as z from 'zod'
import * as common from 'enso-common'

import * as object from '#/utilities/object'
import { IS_DEV_MODE } from 'enso-common/src/detect'
import invariant from 'tiny-invariant'

const KEY_DEFINITION_STACK_TRACES = new Map<string, string>()

/** 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 ===
Expand Down Expand Up @@ -74,9 +87,28 @@ export default class LocalStorage {

/** Register runtime behavior associated with a {@link LocalStorageKey}. */
static registerKey<K extends LocalStorageKey>(key: K, metadata: LocalStorageKeyMetadata<K>) {
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<K extends LocalStorageKey>(metadata: { [K_ in K]: LocalStorageKeyMetadata<K_> }) {
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<K extends LocalStorageKey>(key: K) {
return this.values[key]
Expand Down
4 changes: 2 additions & 2 deletions app/ide-desktop/common/src/text/english.json
Original file line number Diff line number Diff line change
Expand Up @@ -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)",
Expand Down

0 comments on commit 72b4bde

Please sign in to comment.