Skip to content
Source
Sign inSign up

sidebarPendingFileDropStore.ts

54 lines1.9 KB

1import { create } from "zustand";
2​
3import type { ScopedThreadRef } from "@t3tools/contracts";
4​
5/** Compare both ids; scopedThreadKey can collide when ids contain colons. */
6export function isSameSidebarThreadRef(a: ScopedThreadRef, b: ScopedThreadRef): boolean {
7 return a.environmentId === b.environmentId && a.threadId === b.threadId;
8}
9​
10export interface SidebarPendingFileDrop {
11 id: string;
12 threadRef: ScopedThreadRef;
13 files: File[];
15​
16interface SidebarPendingFileDropStoreState {
17 pending: SidebarPendingFileDrop[];
18 queuePendingFileDrop: (entry: Omit<SidebarPendingFileDrop, "id">) => string;
19 clearPendingFileDrop: (id: string) => void;
20 clearPendingFileDropsForThread: (threadRef: ScopedThreadRef) => void;
21 consumePendingFileDrop: (threadRef: ScopedThreadRef) => File[] | null;
23​
24let nextPendingFileDropId = 0;
25​
26export const useSidebarPendingFileDropStore = create<SidebarPendingFileDropStoreState>()(
27 (set, get) => ({
28 pending: [],
29 queuePendingFileDrop: (entry) => {
30 const id = `sidebar-file-drop-${(nextPendingFileDropId += 1)}`;
31 set((state) => ({ pending: [...state.pending, { ...entry, id }] }));
32 return id;
33 },
34 clearPendingFileDrop: (id) => {
35 set((state) => ({ pending: state.pending.filter((drop) => drop.id !== id) }));
36 },
37 clearPendingFileDropsForThread: (threadRef) => {
38 set((state) => ({
39 pending: state.pending.filter((drop) => !isSameSidebarThreadRef(drop.threadRef, threadRef)),
40 }));
41 },
42 consumePendingFileDrop: (threadRef) => {
43 const matches = get().pending.filter((drop) =>
44 isSameSidebarThreadRef(drop.threadRef, threadRef),
45 );
46 if (matches.length === 0) {
47 return null;
48 }
49 const matchedIds = new Set(matches.map((drop) => drop.id));
50 set((state) => ({ pending: state.pending.filter((drop) => !matchedIds.has(drop.id)) }));
51 return matches.flatMap((drop) => drop.files);
52 },
53 }),
54);