mirror of
https://github.com/enso-org/enso.git
synced 2024-11-25 10:43:02 +03:00
Virtualization draft
This commit is contained in:
parent
ab9e7ef9c2
commit
bf098cdc54
@ -67,10 +67,21 @@ export function DialogTrigger(props: DialogTriggerProps) {
|
||||
} satisfies DialogTriggerRenderProps
|
||||
|
||||
return (
|
||||
<aria.DialogTrigger {...state} onOpenChange={onOpenChangeInternal}>
|
||||
<DialogTriggerInner {...state} onOpenChange={onOpenChangeInternal}>
|
||||
{trigger}
|
||||
|
||||
{typeof dialog === 'function' ? dialog(renderProps) : dialog}
|
||||
</aria.DialogTrigger>
|
||||
{state.isOpen ?
|
||||
typeof dialog === 'function' ?
|
||||
dialog(renderProps)
|
||||
: dialog
|
||||
: null}
|
||||
</DialogTriggerInner>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A DialogTriggerInner is a DialogTrigger that has a stable reference.
|
||||
*/
|
||||
function DialogTriggerInner(props: aria.DialogTriggerProps) {
|
||||
return <aria.DialogTrigger {...props} />
|
||||
}
|
||||
|
@ -113,15 +113,12 @@ export interface AssetRowProps {
|
||||
event: React.DragEvent<HTMLTableRowElement>,
|
||||
item: backendModule.AnyAsset,
|
||||
) => void
|
||||
readonly rowHeight: number
|
||||
readonly rowOffset: number
|
||||
}
|
||||
|
||||
/** A row containing an {@link backendModule.AnyAsset}. */
|
||||
export const AssetRow = React.memo(function AssetRow(props: AssetRowProps) {
|
||||
const { id, parentId, isKeyboardSelected, isOpened, select, state, columns, onClick } = props
|
||||
const { path, hidden: hiddenRaw, grabKeyboardFocus, visibility: visibilityRaw, depth } = props
|
||||
const { rowHeight, rowOffset } = props
|
||||
const { initialAssetEvents } = props
|
||||
const { nodeMap, doCopy, doCut, doPaste, doDelete: doDeleteRaw } = state
|
||||
const { doRestore, doMove, category, scrollContainerRef, rootDirectoryId, backend } = state
|
||||
@ -509,10 +506,9 @@ export const AssetRow = React.memo(function AssetRow(props: AssetRowProps) {
|
||||
<>
|
||||
{!hidden && (
|
||||
<FocusRing>
|
||||
<tr
|
||||
<div
|
||||
data-testid="asset-row"
|
||||
tabIndex={0}
|
||||
style={{ maxHeight: rowHeight, transform: `translateY(${rowOffset}px)` }}
|
||||
ref={(element) => {
|
||||
rootRef.current = element
|
||||
|
||||
@ -699,7 +695,7 @@ export const AssetRow = React.memo(function AssetRow(props: AssetRowProps) {
|
||||
</td>
|
||||
)
|
||||
})}
|
||||
</tr>
|
||||
</div>
|
||||
</FocusRing>
|
||||
)}
|
||||
{selected && allowContextMenu && !hidden && (
|
||||
@ -724,7 +720,7 @@ export const AssetRow = React.memo(function AssetRow(props: AssetRowProps) {
|
||||
}
|
||||
case backendModule.AssetType.specialLoading: {
|
||||
return hidden ? null : (
|
||||
<tr>
|
||||
<div>
|
||||
<td colSpan={columns.length} className="border-r p-0 rounded-rows-skip-level">
|
||||
<div
|
||||
className={tailwindMerge.twMerge(
|
||||
@ -735,12 +731,12 @@ export const AssetRow = React.memo(function AssetRow(props: AssetRowProps) {
|
||||
<StatelessSpinner size={24} state={statelessSpinner.SpinnerState.loadingMedium} />
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
case backendModule.AssetType.specialEmpty: {
|
||||
return hidden ? null : (
|
||||
<tr>
|
||||
<div>
|
||||
<td colSpan={columns.length} className="border-r p-0 rounded-rows-skip-level">
|
||||
<div
|
||||
className={tailwindMerge.twMerge(
|
||||
@ -754,12 +750,12 @@ export const AssetRow = React.memo(function AssetRow(props: AssetRowProps) {
|
||||
</Text>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
case backendModule.AssetType.specialError: {
|
||||
return hidden ? null : (
|
||||
<tr>
|
||||
<div>
|
||||
<td colSpan={columns.length} className="border-r p-0 rounded-rows-skip-level">
|
||||
<div
|
||||
className={tailwindMerge.twMerge(
|
||||
@ -776,7 +772,7 @@ export const AssetRow = React.memo(function AssetRow(props: AssetRowProps) {
|
||||
</Text>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import SvgMask from '#/components/SvgMask'
|
||||
|
||||
import * as backendModule from '#/services/Backend'
|
||||
|
||||
import { useEventCallback } from '#/hooks/eventCallbackHooks'
|
||||
import * as eventModule from '#/utilities/event'
|
||||
import * as indent from '#/utilities/indent'
|
||||
import * as object from '#/utilities/object'
|
||||
@ -46,7 +47,7 @@ export default function DirectoryNameColumn(props: DirectoryNameColumnProps) {
|
||||
|
||||
const updateDirectoryMutation = useMutation(backendMutationOptions(backend, 'updateDirectory'))
|
||||
|
||||
const setIsEditing = (isEditingName: boolean) => {
|
||||
const setIsEditing = useEventCallback((isEditingName: boolean) => {
|
||||
if (isEditable) {
|
||||
setRowState(object.merger({ isEditingName }))
|
||||
}
|
||||
@ -54,20 +55,38 @@ export default function DirectoryNameColumn(props: DirectoryNameColumnProps) {
|
||||
if (!isEditingName) {
|
||||
driveStore.setState({ newestFolderId: null })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const doRename = async (newTitle: string) => {
|
||||
const doRename = useEventCallback(async (newTitle: string) => {
|
||||
if (isEditable) {
|
||||
setIsEditing(false)
|
||||
if (!string.isWhitespaceOnly(newTitle) && newTitle !== item.title) {
|
||||
await updateDirectoryMutation.mutateAsync([item.id, { title: newTitle }, item.title])
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const toggleExpanded = useEventCallback(() => {
|
||||
doToggleDirectoryExpansion(item.id, item.id)
|
||||
})
|
||||
|
||||
const checkSubmittable = useEventCallback(
|
||||
(newTitle: string) =>
|
||||
validation.DIRECTORY_NAME_REGEX.test(newTitle) &&
|
||||
backendModule.isNewTitleValid(
|
||||
item,
|
||||
newTitle,
|
||||
nodeMap.current.get(item.parentId)?.children?.map((child) => child.item),
|
||||
),
|
||||
)
|
||||
|
||||
const onCancel = useEventCallback(() => {
|
||||
setIsEditing(false)
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
className={tailwindMerge.twMerge(
|
||||
className={tailwindMerge.twJoin(
|
||||
'group flex h-table-row min-w-max items-center gap-name-column-icon whitespace-nowrap rounded-l-full px-name-column-x py-name-column-y',
|
||||
indent.indentClass(depth),
|
||||
)}
|
||||
@ -93,34 +112,23 @@ export default function DirectoryNameColumn(props: DirectoryNameColumnProps) {
|
||||
variant="custom"
|
||||
aria-label={isExpanded ? getText('collapse') : getText('expand')}
|
||||
tooltipPlacement="left"
|
||||
className={tailwindMerge.twMerge(
|
||||
className={tailwindMerge.twJoin(
|
||||
'm-0 hidden cursor-pointer border-0 transition-transform duration-arrow group-hover:m-name-column-icon group-hover:inline-block',
|
||||
isExpanded && 'rotate-90',
|
||||
)}
|
||||
onPress={() => {
|
||||
doToggleDirectoryExpansion(item.id, item.id)
|
||||
}}
|
||||
onPress={toggleExpanded}
|
||||
/>
|
||||
<SvgMask src={FolderIcon} className="m-name-column-icon size-4 group-hover:hidden" />
|
||||
<EditableSpan
|
||||
data-testid="asset-row-name"
|
||||
editable={rowState.isEditingName}
|
||||
className={tailwindMerge.twMerge(
|
||||
className={tailwindMerge.twJoin(
|
||||
'grow cursor-pointer bg-transparent font-naming',
|
||||
rowState.isEditingName ? 'cursor-text' : 'cursor-pointer',
|
||||
)}
|
||||
checkSubmittable={(newTitle) =>
|
||||
validation.DIRECTORY_NAME_REGEX.test(newTitle) &&
|
||||
backendModule.isNewTitleValid(
|
||||
item,
|
||||
newTitle,
|
||||
nodeMap.current.get(item.parentId)?.children?.map((child) => child.item),
|
||||
)
|
||||
}
|
||||
checkSubmittable={checkSubmittable}
|
||||
onSubmit={doRename}
|
||||
onCancel={() => {
|
||||
setIsEditing(false)
|
||||
}}
|
||||
onCancel={onCancel}
|
||||
>
|
||||
{item.title}
|
||||
</EditableSpan>
|
||||
|
@ -20,6 +20,7 @@ import * as backendModule from '#/services/Backend'
|
||||
import { useBackendQuery } from '#/hooks/backendHooks'
|
||||
import * as tailwindMerge from '#/utilities/tailwindMerge'
|
||||
import { useMemo } from 'react'
|
||||
import { useEventCallback } from '#/hooks/eventCallbackHooks'
|
||||
|
||||
// =================
|
||||
// === Constants ===
|
||||
@ -137,15 +138,15 @@ export default function ProjectIcon(props: ProjectIconProps) {
|
||||
}
|
||||
})()
|
||||
|
||||
const doOpenProject = () => {
|
||||
const doOpenProject = useEventCallback(() => {
|
||||
openProject({ ...item, type: backend.type })
|
||||
}
|
||||
const doCloseProject = () => {
|
||||
})
|
||||
const doCloseProject = useEventCallback(() => {
|
||||
closeProject({ ...item, type: backend.type })
|
||||
}
|
||||
const doOpenProjectTab = () => {
|
||||
})
|
||||
const doOpenProjectTab = useEventCallback(() => {
|
||||
openProjectTab(item.id)
|
||||
}
|
||||
})
|
||||
|
||||
switch (state) {
|
||||
case backendModule.ProjectState.new:
|
||||
|
@ -20,6 +20,7 @@ import ManageLabelsModal from '#/modals/ManageLabelsModal'
|
||||
|
||||
import * as backendModule from '#/services/Backend'
|
||||
|
||||
import { useEventCallback } from '#/hooks/eventCallbackHooks'
|
||||
import * as permissions from '#/utilities/permissions'
|
||||
|
||||
// ====================
|
||||
@ -44,6 +45,12 @@ export default function LabelsColumn(props: column.AssetColumnProps) {
|
||||
(self?.permission === permissions.PermissionAction.own ||
|
||||
self?.permission === permissions.PermissionAction.admin)
|
||||
|
||||
const onPress = useEventCallback(() => {})
|
||||
|
||||
const renderManageLabelsModal = useEventCallback(() => (
|
||||
<ManageLabelsModal backend={backend} item={item} />
|
||||
))
|
||||
|
||||
return (
|
||||
<div className="group flex items-center gap-column-items">
|
||||
{(item.labels ?? [])
|
||||
@ -93,7 +100,7 @@ export default function LabelsColumn(props: column.AssetColumnProps) {
|
||||
isDisabled
|
||||
key={label}
|
||||
color={labelsByName.get(label)?.color ?? backendModule.COLORS[0]}
|
||||
onPress={() => {}}
|
||||
onPress={onPress}
|
||||
>
|
||||
{label}
|
||||
</Label>
|
||||
@ -101,7 +108,7 @@ export default function LabelsColumn(props: column.AssetColumnProps) {
|
||||
{managesThisAsset && (
|
||||
<DialogTrigger>
|
||||
<Button variant="ghost" showIconOnHover icon={Plus2Icon} />
|
||||
<ManageLabelsModal backend={backend} item={item} />
|
||||
{renderManageLabelsModal}
|
||||
</DialogTrigger>
|
||||
)}
|
||||
</div>
|
||||
|
@ -2574,7 +2574,7 @@ export default function AssetsTable(props: AssetsTableProps) {
|
||||
count: displayItems.length,
|
||||
getScrollElement: () => rootRef.current,
|
||||
estimateSize: () => ROW_HEIGHT_PX,
|
||||
overscan: 20,
|
||||
overscan: 0,
|
||||
})
|
||||
|
||||
const columns = useMemo(
|
||||
@ -2614,10 +2614,16 @@ export default function AssetsTable(props: AssetsTableProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<tr
|
||||
key={index}
|
||||
data-testid="asset-row"
|
||||
tabIndex={0}
|
||||
style={{
|
||||
maxHeight: virtualRow.size,
|
||||
transform: `translateY(${virtualRow.start - index * virtualRow.size}px)`,
|
||||
}}
|
||||
>
|
||||
<AssetRow
|
||||
key={item.key + item.path}
|
||||
rowHeight={virtualRow.size}
|
||||
rowOffset={virtualRow.start - index * virtualRow.size}
|
||||
isOpened={openedProjects.some(({ id }) => item.item.id === id)}
|
||||
visibility={visibilities.get(item.key)}
|
||||
columns={columns}
|
||||
@ -2639,6 +2645,7 @@ export default function AssetsTable(props: AssetsTableProps) {
|
||||
onDragEnd={onRowDragEnd}
|
||||
onDrop={onRowDrop}
|
||||
/>
|
||||
</tr>
|
||||
)
|
||||
})
|
||||
|
||||
@ -2690,7 +2697,7 @@ export default function AssetsTable(props: AssetsTableProps) {
|
||||
}}
|
||||
>
|
||||
<table className="isolate table-fixed border-collapse rounded-rows">
|
||||
<thead className="sticky top-0 z-1 bg-dashboard">{headerRow}</thead>
|
||||
<thead className="bg-dashboard sticky top-0 z-1">{headerRow}</thead>
|
||||
<tbody ref={bodyRef}>
|
||||
{itemRows}
|
||||
<tr className="hidden h-row first:table-row">
|
||||
@ -2792,6 +2799,7 @@ export default function AssetsTable(props: AssetsTableProps) {
|
||||
{...mergeProps<JSX.IntrinsicElements['div']>()(innerProps, {
|
||||
className: 'flex-1 overflow-auto container-size w-full h-full',
|
||||
onKeyDown,
|
||||
ref: rootRef,
|
||||
onBlur: (event) => {
|
||||
if (
|
||||
event.relatedTarget instanceof HTMLElement &&
|
||||
|
Loading…
Reference in New Issue
Block a user