mirror of
https://github.com/tiddly-gittly/TidGi-Desktop.git
synced 2026-01-21 12:02:57 -08:00
fix: drag
This commit is contained in:
parent
302deb213f
commit
d767c9bce1
4 changed files with 185 additions and 65 deletions
|
|
@ -9,6 +9,14 @@ interface ItemMoveCallbacks {
|
|||
onMoveDown: () => void;
|
||||
}
|
||||
|
||||
/** Counter for generating unique IDs */
|
||||
let itemIdCounter = 0;
|
||||
|
||||
/** Generate a unique stable ID for an array item */
|
||||
function generateItemId(): string {
|
||||
return `item-${Date.now()}-${itemIdCounter++}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store for managing array field state
|
||||
* Tracks expanded state and ordering for each array field instance
|
||||
|
|
@ -18,10 +26,14 @@ interface ArrayFieldState {
|
|||
expandedStates: Record<string, boolean[]>;
|
||||
/** Map of field path -> array of item data (for stable rendering) */
|
||||
itemsData: Record<string, unknown[]>;
|
||||
/** Map of field path -> array order (indices) */
|
||||
/** Map of field path -> array order (indices) - used for optimistic rendering during drag */
|
||||
itemsOrder: Record<string, number[]>;
|
||||
/** Map of field path -> index -> move callbacks */
|
||||
moveCallbacks: Record<string, Record<number, ItemMoveCallbacks>>;
|
||||
/** Map of field path -> array of stable unique IDs for dnd-kit */
|
||||
stableItemIds: Record<string, string[]>;
|
||||
/** Map of field path -> whether there's a pending reorder (optimistic update in progress) */
|
||||
pendingReorder: Record<string, boolean>;
|
||||
}
|
||||
|
||||
interface ArrayFieldActions {
|
||||
|
|
@ -29,9 +41,9 @@ interface ArrayFieldActions {
|
|||
setItemExpanded: (fieldPath: string, itemIndex: number, expanded: boolean) => void;
|
||||
/** Initialize array field state */
|
||||
initializeField: (fieldPath: string, itemCount: number, itemsData: unknown[]) => void;
|
||||
/** Move item to new position */
|
||||
/** Move item to new position (optimistic update for drag-and-drop) */
|
||||
moveItem: (fieldPath: string, oldIndex: number, newIndex: number) => void;
|
||||
/** Update items data when form data changes */
|
||||
/** Update items data when form data changes (from RJSF) */
|
||||
updateItemsData: (fieldPath: string, itemsData: unknown[]) => void;
|
||||
/** Clean up field state when unmounted */
|
||||
cleanupField: (fieldPath: string) => void;
|
||||
|
|
@ -46,6 +58,8 @@ const initialState: ArrayFieldState = {
|
|||
itemsData: {},
|
||||
itemsOrder: {},
|
||||
moveCallbacks: {},
|
||||
stableItemIds: {},
|
||||
pendingReorder: {},
|
||||
};
|
||||
|
||||
export const useArrayFieldStore = create<ArrayFieldState & ArrayFieldActions>()(
|
||||
|
|
@ -67,6 +81,7 @@ export const useArrayFieldStore = create<ArrayFieldState & ArrayFieldActions>()(
|
|||
if (!state.expandedStates[fieldPath] || state.expandedStates[fieldPath].length !== itemCount) {
|
||||
state.expandedStates[fieldPath] = Array.from({ length: itemCount }, () => false);
|
||||
state.itemsOrder[fieldPath] = Array.from({ length: itemCount }, (_, index) => index);
|
||||
state.stableItemIds[fieldPath] = Array.from({ length: itemCount }, () => generateItemId());
|
||||
}
|
||||
state.itemsData[fieldPath] = itemsData;
|
||||
});
|
||||
|
|
@ -76,7 +91,11 @@ export const useArrayFieldStore = create<ArrayFieldState & ArrayFieldActions>()(
|
|||
set((state) => {
|
||||
const order = state.itemsOrder[fieldPath];
|
||||
const expanded = state.expandedStates[fieldPath];
|
||||
if (!order || !expanded) return;
|
||||
const stableIds = state.stableItemIds[fieldPath];
|
||||
if (!order || !expanded || !stableIds) return;
|
||||
|
||||
// Mark that we have a pending reorder (optimistic update)
|
||||
state.pendingReorder[fieldPath] = true;
|
||||
|
||||
// Move in order array
|
||||
const [movedOrderItem] = order.splice(oldIndex, 1);
|
||||
|
|
@ -85,6 +104,10 @@ export const useArrayFieldStore = create<ArrayFieldState & ArrayFieldActions>()(
|
|||
// Move in expanded states
|
||||
const [movedExpandedItem] = expanded.splice(oldIndex, 1);
|
||||
expanded.splice(newIndex, 0, movedExpandedItem);
|
||||
|
||||
// Move in stable IDs array
|
||||
const [movedStableId] = stableIds.splice(oldIndex, 1);
|
||||
stableIds.splice(newIndex, 0, movedStableId);
|
||||
});
|
||||
},
|
||||
|
||||
|
|
@ -92,10 +115,11 @@ export const useArrayFieldStore = create<ArrayFieldState & ArrayFieldActions>()(
|
|||
set((state) => {
|
||||
const previousLength = state.itemsData[fieldPath]?.length ?? 0;
|
||||
const newLength = itemsData.length;
|
||||
const hasPendingReorder = state.pendingReorder[fieldPath] ?? false;
|
||||
|
||||
state.itemsData[fieldPath] = itemsData;
|
||||
|
||||
// Adjust order and expanded states if length changed
|
||||
// Adjust order, expanded states, and stable IDs if length changed
|
||||
if (newLength !== previousLength) {
|
||||
if (newLength > previousLength) {
|
||||
// Items added
|
||||
|
|
@ -106,9 +130,13 @@ export const useArrayFieldStore = create<ArrayFieldState & ArrayFieldActions>()(
|
|||
if (!state.expandedStates[fieldPath]) {
|
||||
state.expandedStates[fieldPath] = [];
|
||||
}
|
||||
if (!state.stableItemIds[fieldPath]) {
|
||||
state.stableItemIds[fieldPath] = [];
|
||||
}
|
||||
for (let addedIndex = 0; addedIndex < addedCount; addedIndex++) {
|
||||
state.itemsOrder[fieldPath].push(previousLength + addedIndex);
|
||||
state.expandedStates[fieldPath].push(false);
|
||||
state.stableItemIds[fieldPath].push(generateItemId());
|
||||
}
|
||||
} else {
|
||||
// Items removed
|
||||
|
|
@ -120,7 +148,17 @@ export const useArrayFieldStore = create<ArrayFieldState & ArrayFieldActions>()(
|
|||
if (state.expandedStates[fieldPath]) {
|
||||
state.expandedStates[fieldPath].splice(newLength);
|
||||
}
|
||||
if (state.stableItemIds[fieldPath]) {
|
||||
state.stableItemIds[fieldPath].splice(newLength);
|
||||
}
|
||||
}
|
||||
// Clear pending reorder flag on length change
|
||||
state.pendingReorder[fieldPath] = false;
|
||||
} else if (hasPendingReorder && state.itemsOrder[fieldPath]) {
|
||||
// Length is same and we had a pending reorder - RJSF has re-rendered with updated data
|
||||
// Reset itemsOrder to natural order since items array now reflects the new order
|
||||
state.itemsOrder[fieldPath] = Array.from({ length: newLength }, (_, index) => index);
|
||||
state.pendingReorder[fieldPath] = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
|
@ -139,6 +177,12 @@ export const useArrayFieldStore = create<ArrayFieldState & ArrayFieldActions>()(
|
|||
state.moveCallbacks = Object.fromEntries(
|
||||
Object.entries(state.moveCallbacks).filter(([key]) => key !== fieldPath),
|
||||
);
|
||||
state.stableItemIds = Object.fromEntries(
|
||||
Object.entries(state.stableItemIds).filter(([key]) => key !== fieldPath),
|
||||
);
|
||||
state.pendingReorder = Object.fromEntries(
|
||||
Object.entries(state.pendingReorder).filter(([key]) => key !== fieldPath),
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { useSortable } from '@dnd-kit/sortable';
|
||||
import { defaultAnimateLayoutChanges, useSortable } from '@dnd-kit/sortable';
|
||||
import type { AnimateLayoutChanges } from '@dnd-kit/sortable';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import DragHandleIcon from '@mui/icons-material/DragHandle';
|
||||
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
|
||||
|
|
@ -10,6 +11,12 @@ import { useTranslation } from 'react-i18next';
|
|||
import { ArrayItemProvider, useArrayItemContext } from '../context/ArrayItemContext';
|
||||
import { useArrayFieldStore } from '../store/arrayFieldStore';
|
||||
|
||||
/**
|
||||
* Custom animateLayoutChanges that always animates when wasDragging is true.
|
||||
* This ensures smooth transitions after drag ends when items are reordered.
|
||||
*/
|
||||
const animateLayoutChanges: AnimateLayoutChanges = (arguments_) => defaultAnimateLayoutChanges({ ...arguments_, wasDragging: true });
|
||||
|
||||
/**
|
||||
* Custom Array Field Item Template with collapse and dnd-kit drag-and-drop support
|
||||
* Uses zustand store for state management to avoid re-render flashing
|
||||
|
|
@ -32,6 +39,10 @@ export function ArrayFieldItemTemplate<T = unknown, S extends RJSFSchema = RJSFS
|
|||
const expanded = useArrayFieldStore(
|
||||
useCallback((state) => state.expandedStates[fieldPath]?.[index] ?? false, [fieldPath, index]),
|
||||
);
|
||||
// Get stable item ID from store for dnd-kit
|
||||
const stableItemId = useArrayFieldStore(
|
||||
useCallback((state) => state.stableItemIds[fieldPath]?.[index] ?? `item-${index}`, [fieldPath, index]),
|
||||
);
|
||||
const setItemExpanded = useArrayFieldStore((state) => state.setItemExpanded);
|
||||
const registerMoveCallbacks = useArrayFieldStore((state) => state.registerMoveCallbacks);
|
||||
|
||||
|
|
@ -45,10 +56,10 @@ export function ArrayFieldItemTemplate<T = unknown, S extends RJSFSchema = RJSFS
|
|||
}
|
||||
}, [fieldPath, index, buttonsProps.onMoveUpItem, buttonsProps.onMoveDownItem, buttonsProps.hasMoveUp, buttonsProps.hasMoveDown, registerMoveCallbacks]);
|
||||
|
||||
// Use dnd-kit sortable
|
||||
const sortableId = `item-${index}`;
|
||||
// Use dnd-kit sortable with stable ID from store
|
||||
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
||||
id: sortableId,
|
||||
id: stableItemId,
|
||||
animateLayoutChanges,
|
||||
});
|
||||
|
||||
const handleToggleExpanded = useCallback(() => {
|
||||
|
|
@ -80,7 +91,7 @@ export function ArrayFieldItemTemplate<T = unknown, S extends RJSFSchema = RJSFS
|
|||
|
||||
return (
|
||||
<Box
|
||||
id={sortableId}
|
||||
id={stableItemId}
|
||||
ref={setNodeRef}
|
||||
style={style}
|
||||
sx={{
|
||||
|
|
|
|||
|
|
@ -32,6 +32,11 @@ export const ArrayFieldTemplate: React.FC<ArrayFieldTemplateProps> = (props) =>
|
|||
const initializeField = useArrayFieldStore((state) => state.initializeField);
|
||||
const updateItemsData = useArrayFieldStore((state) => state.updateItemsData);
|
||||
const cleanupField = useArrayFieldStore((state) => state.cleanupField);
|
||||
const moveItem = useArrayFieldStore((state) => state.moveItem);
|
||||
// Get stable item IDs from store - these persist across re-renders
|
||||
const stableItemIds = useArrayFieldStore((state) => state.stableItemIds[fieldPath] ?? []);
|
||||
// Get items order from store - used for optimistic rendering during drag
|
||||
const itemsOrder = useArrayFieldStore((state) => state.itemsOrder[fieldPath] ?? []);
|
||||
|
||||
// Track active drag item for overlay
|
||||
const [activeId, setActiveId] = useState<string | null>(null);
|
||||
|
|
@ -42,7 +47,7 @@ export const ArrayFieldTemplate: React.FC<ArrayFieldTemplateProps> = (props) =>
|
|||
initializeField(fieldPath, items.length, itemsData);
|
||||
}, [fieldPath, items.length, initializeField]);
|
||||
|
||||
// Update store when formData changes
|
||||
// Update store when formData changes (from RJSF)
|
||||
useEffect(() => {
|
||||
const itemsData = Array.isArray(formData) ? formData : [];
|
||||
updateItemsData(fieldPath, itemsData);
|
||||
|
|
@ -64,10 +69,14 @@ export const ArrayFieldTemplate: React.FC<ArrayFieldTemplateProps> = (props) =>
|
|||
}),
|
||||
);
|
||||
|
||||
// Generate stable IDs for sortable items
|
||||
// Use stable IDs from store, fallback to index-based IDs if store not initialized yet
|
||||
const itemIds = useMemo(() => {
|
||||
if (stableItemIds.length === items.length) {
|
||||
return stableItemIds;
|
||||
}
|
||||
// Fallback during initialization
|
||||
return items.map((_, index) => `item-${index}`);
|
||||
}, [items.length]);
|
||||
}, [stableItemIds, items.length]);
|
||||
|
||||
const handleDragStart = useCallback((event: DragStartEvent) => {
|
||||
setActiveId(String(event.active.id));
|
||||
|
|
@ -77,19 +86,33 @@ export const ArrayFieldTemplate: React.FC<ArrayFieldTemplateProps> = (props) =>
|
|||
const { active, over } = event;
|
||||
setActiveId(null);
|
||||
|
||||
if (!over || active.id === over.id) return;
|
||||
if (!over || active.id === over.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldIndex = Number.parseInt(String(active.id).replace('item-', ''), 10);
|
||||
const newIndex = Number.parseInt(String(over.id).replace('item-', ''), 10);
|
||||
// Find indices by looking up the stable IDs
|
||||
const oldIndex = itemIds.indexOf(String(active.id));
|
||||
const newIndex = itemIds.indexOf(String(over.id));
|
||||
|
||||
if (Number.isNaN(oldIndex) || Number.isNaN(newIndex)) return;
|
||||
if (!formData || !formContext?.onFormDataChange || !formContext.rootFormData) return;
|
||||
if (oldIndex === -1 || newIndex === -1) {
|
||||
return;
|
||||
}
|
||||
if (!formData || !formContext?.onFormDataChange || !formContext.rootFormData) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use arrayMove from dnd-kit to reorder the array
|
||||
// IMPORTANT: Update store state FIRST (synchronously) for optimistic rendering
|
||||
// This moves stableItemIds, expandedStates, and itemsOrder together
|
||||
// The component will immediately re-render with the new order, avoiding the
|
||||
// ~500-700ms delay while waiting for RJSF to update formData
|
||||
moveItem(fieldPath, oldIndex, newIndex);
|
||||
|
||||
// Create the new array data with reordered items
|
||||
const newArrayData = arrayMove([...formData], oldIndex, newIndex);
|
||||
|
||||
// Update the root form data with the reordered array
|
||||
// Navigate to the array location using fieldPathId.path
|
||||
// This triggers RJSF to re-render, but the UI already shows the new order
|
||||
// thanks to the optimistic update above
|
||||
const path = fieldPathId?.path;
|
||||
if (!path || path.length === 0) {
|
||||
// If no path, this array is the root (unlikely but handle it)
|
||||
|
|
@ -112,19 +135,19 @@ export const ArrayFieldTemplate: React.FC<ArrayFieldTemplateProps> = (props) =>
|
|||
current[finalKey] = newArrayData;
|
||||
|
||||
formContext.onFormDataChange(newRootData as never);
|
||||
}, [formData, formContext, fieldPathId]);
|
||||
}, [formData, formContext, fieldPathId, itemIds, moveItem, fieldPath]);
|
||||
|
||||
const handleDragCancel = useCallback(() => {
|
||||
setActiveId(null);
|
||||
}, []);
|
||||
|
||||
// Find active item for drag overlay
|
||||
// Find active item for drag overlay using stable IDs
|
||||
const activeItem = useMemo(() => {
|
||||
if (!activeId) return null;
|
||||
const activeIndex = Number.parseInt(activeId.replace('item-', ''), 10);
|
||||
if (Number.isNaN(activeIndex)) return null;
|
||||
const activeIndex = itemIds.indexOf(activeId);
|
||||
if (activeIndex === -1) return null;
|
||||
return items[activeIndex];
|
||||
}, [activeId, items]);
|
||||
}, [activeId, items, itemIds]);
|
||||
|
||||
return (
|
||||
<ArrayContainer>
|
||||
|
|
@ -163,16 +186,22 @@ export const ArrayFieldTemplate: React.FC<ArrayFieldTemplateProps> = (props) =>
|
|||
>
|
||||
<SortableContext items={itemIds} strategy={verticalListSortingStrategy}>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
{items.map((item, index) => {
|
||||
const itemData = formData?.[index];
|
||||
{itemsOrder.map((originalIndex, renderPosition) => {
|
||||
// itemsOrder[renderPosition] tells us which original item should be at this position
|
||||
// This enables optimistic rendering - store updates immediately, RJSF updates later
|
||||
const item = items[originalIndex];
|
||||
const itemData = formData?.[originalIndex];
|
||||
const stableId = itemIds[renderPosition];
|
||||
|
||||
if (!item) return null;
|
||||
|
||||
return (
|
||||
<ArrayItemProvider
|
||||
key={itemIds[index]}
|
||||
key={stableId}
|
||||
isInArrayItem
|
||||
arrayItemCollapsible
|
||||
itemData={itemData}
|
||||
itemIndex={index}
|
||||
itemIndex={renderPosition}
|
||||
arrayFieldPath={fieldPath}
|
||||
>
|
||||
{item}
|
||||
|
|
@ -181,7 +210,7 @@ export const ArrayFieldTemplate: React.FC<ArrayFieldTemplateProps> = (props) =>
|
|||
})}
|
||||
</Box>
|
||||
</SortableContext>
|
||||
<DragOverlay>
|
||||
<DragOverlay dropAnimation={null}>
|
||||
{activeItem
|
||||
? (
|
||||
<Box
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
import { DndContext, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
|
||||
import { DndContext, DragEndEvent, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
|
||||
import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
|
||||
import { arrayMove, SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
|
||||
import { useMemo } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { PageType } from '@/constants/pageTypes';
|
||||
import { WindowNames } from '@services/windows/WindowProperties';
|
||||
import { IWorkspace, IWorkspaceWithMetadata } from '@services/workspaces/interface';
|
||||
import { workspaceSorter } from '@services/workspaces/utilities';
|
||||
import { SortableWorkspaceSelectorButton } from './SortableWorkspaceSelectorButton';
|
||||
|
||||
export interface ISortableListProps {
|
||||
|
|
@ -26,53 +25,90 @@ export function SortableWorkspaceSelectorList({ workspacesList, showSideBarText,
|
|||
|
||||
const isMiniWindow = window.meta().windowName === WindowNames.tidgiMiniWindow;
|
||||
|
||||
// Optimistic order state - stores workspace IDs in the order they should be displayed
|
||||
// This updates immediately on drag end, before the backend confirms the change
|
||||
const [optimisticOrder, setOptimisticOrder] = useState<string[] | null>(null);
|
||||
// Track if we're waiting for backend to confirm the reorder
|
||||
const pendingReorderReference = useRef<boolean>(false);
|
||||
|
||||
// Filter out 'add' workspace in mini window
|
||||
const filteredWorkspacesList = useMemo(() => {
|
||||
const baseFilteredList = useMemo(() => {
|
||||
if (isMiniWindow) {
|
||||
return workspacesList.filter((workspace) => workspace.pageType !== PageType.add);
|
||||
}
|
||||
return workspacesList;
|
||||
}, [isMiniWindow, workspacesList]);
|
||||
|
||||
// Apply optimistic order if present, otherwise use natural order from props
|
||||
const filteredWorkspacesList = useMemo(() => {
|
||||
if (optimisticOrder === null) {
|
||||
// No optimistic order, sort by order property
|
||||
return [...baseFilteredList].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
||||
}
|
||||
// Apply optimistic order
|
||||
const orderMap = new Map(optimisticOrder.map((id, index) => [id, index]));
|
||||
return [...baseFilteredList].sort((a, b) => {
|
||||
const orderA = orderMap.get(a.id) ?? a.order ?? 0;
|
||||
const orderB = orderMap.get(b.id) ?? b.order ?? 0;
|
||||
return orderA - orderB;
|
||||
});
|
||||
}, [baseFilteredList, optimisticOrder]);
|
||||
|
||||
// When workspacesList updates from backend, clear optimistic order if pending
|
||||
useEffect(() => {
|
||||
if (pendingReorderReference.current) {
|
||||
pendingReorderReference.current = false;
|
||||
setOptimisticOrder(null);
|
||||
}
|
||||
}, [workspacesList]);
|
||||
|
||||
const workspaceIDs = filteredWorkspacesList.map((workspace) => workspace.id);
|
||||
|
||||
const handleDragEnd = useCallback(async (event: DragEndEvent) => {
|
||||
const { active, over } = event;
|
||||
if (over === null || active.id === over.id) return;
|
||||
|
||||
const activeId = String(active.id);
|
||||
const overId = String(over.id);
|
||||
|
||||
const oldIndex = filteredWorkspacesList.findIndex(workspace => workspace.id === activeId);
|
||||
const newIndex = filteredWorkspacesList.findIndex(workspace => workspace.id === overId);
|
||||
|
||||
if (oldIndex === -1 || newIndex === -1) return;
|
||||
|
||||
// OPTIMISTIC UPDATE: Immediately update the display order
|
||||
const newOrderedList = arrayMove(filteredWorkspacesList, oldIndex, newIndex);
|
||||
const newOrder = newOrderedList.map(w => w.id);
|
||||
setOptimisticOrder(newOrder);
|
||||
pendingReorderReference.current = true;
|
||||
|
||||
// Prepare data for backend update
|
||||
const newWorkspaces: Record<string, IWorkspace> = {};
|
||||
newOrderedList.forEach((workspace, index) => {
|
||||
newWorkspaces[workspace.id] = { ...workspace };
|
||||
newWorkspaces[workspace.id].order = index;
|
||||
});
|
||||
|
||||
// Update backend (this will eventually trigger workspacesList update via Observable)
|
||||
await window.service.workspace.setWorkspaces(newWorkspaces);
|
||||
}, [filteredWorkspacesList]);
|
||||
|
||||
return (
|
||||
<DndContext
|
||||
sensors={dndSensors}
|
||||
modifiers={[restrictToVerticalAxis]}
|
||||
onDragEnd={async ({ active, over }) => {
|
||||
if (over === null || active.id === over.id) return;
|
||||
|
||||
const activeId = String(active.id);
|
||||
const overId = String(over.id);
|
||||
|
||||
const oldIndex = workspacesList.findIndex(workspace => workspace.id === activeId);
|
||||
const newIndex = workspacesList.findIndex(workspace => workspace.id === overId);
|
||||
|
||||
if (oldIndex === -1 || newIndex === -1) return;
|
||||
|
||||
const newWorkspacesList = arrayMove(workspacesList, oldIndex, newIndex);
|
||||
const newWorkspaces: Record<string, IWorkspace> = {};
|
||||
newWorkspacesList.forEach((workspace, index) => {
|
||||
newWorkspaces[workspace.id] = workspace;
|
||||
newWorkspaces[workspace.id].order = index;
|
||||
});
|
||||
|
||||
await window.service.workspace.setWorkspaces(newWorkspaces);
|
||||
}}
|
||||
onDragEnd={handleDragEnd}
|
||||
>
|
||||
<SortableContext items={workspaceIDs} strategy={verticalListSortingStrategy}>
|
||||
{filteredWorkspacesList
|
||||
.sort(workspaceSorter)
|
||||
.map((workspace, index) => (
|
||||
<SortableWorkspaceSelectorButton
|
||||
key={`item-${workspace.id}`}
|
||||
index={index}
|
||||
workspace={workspace}
|
||||
showSidebarTexts={showSideBarText}
|
||||
showSideBarIcon={showSideBarIcon}
|
||||
/>
|
||||
))}
|
||||
{filteredWorkspacesList.map((workspace, index) => (
|
||||
<SortableWorkspaceSelectorButton
|
||||
key={`item-${workspace.id}`}
|
||||
index={index}
|
||||
workspace={workspace}
|
||||
showSidebarTexts={showSideBarText}
|
||||
showSideBarIcon={showSideBarIcon}
|
||||
/>
|
||||
))}
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue