@oxyhq/services
Version:
Reusable OxyHQ module to handle authentication, user management, karma system, device-based session management and more 🚀
119 lines (113 loc) • 4.61 kB
text/typescript
import { create } from 'zustand';
import { shallow } from 'zustand/shallow';
import type { FileMetadata } from '../../models/interfaces';
// Shallow compare two file metadata objects by keys/values
function shallowEqualFile(a: FileMetadata, b: FileMetadata): boolean {
if (a === b) return true;
if (!a || !b) return false;
const aKeys = Object.keys(a) as Array<keyof FileMetadata>;
const bKeys = Object.keys(b) as Array<keyof FileMetadata>;
if (aKeys.length !== bKeys.length) return false;
for (const k of aKeys) {
// treat metadata/variants shallowly by reference
if ((a as any)[k] !== (b as any)[k]) return false;
}
return true;
}
// Basic upload progress type for aggregate tracking
export interface FileUploadAggregateProgress {
current: number;
total: number;
}
interface FileState {
files: Record<string, FileMetadata>;
order: string[]; // maintain insertion / sort order
uploading: boolean;
deleting: string | null;
uploadProgress: FileUploadAggregateProgress | null;
// actions
setFiles: (files: FileMetadata[], opts?: { merge?: boolean }) => void;
addFile: (file: FileMetadata, opts?: { prepend?: boolean }) => void;
updateFile: (id: string, patch: Partial<FileMetadata>) => void;
removeFile: (id: string) => void;
setUploading: (val: boolean) => void;
setDeleting: (id: string | null) => void;
setUploadProgress: (p: FileUploadAggregateProgress | null) => void;
reset: () => void;
}
const initialState = {
files: {} as Record<string, FileMetadata>,
order: [] as string[],
uploading: false,
deleting: null as string | null,
uploadProgress: null as FileUploadAggregateProgress | null,
};
export const useFileStore = create<FileState>((set, get) => ({
...initialState,
setFiles: (files, opts) => set(state => {
const merge = opts?.merge !== false; // default true
if (!merge) {
const map: Record<string, FileMetadata> = {};
const order: string[] = [];
files.forEach(f => { map[f.id] = f; order.push(f.id); });
// detect if identical to avoid redundant updates
const sameOrder = order.length === state.order.length && order.every((id, i) => id === state.order[i]);
let sameFiles = sameOrder;
if (sameOrder) {
sameFiles = order.every(id => state.files[id] && shallowEqualFile(state.files[id], map[id] as FileMetadata));
}
if (sameOrder && sameFiles) return {} as any;
return { files: map, order };
}
const newFiles = { ...state.files };
const newOrder = [...state.order];
let changed = false;
files.forEach(f => {
const prev = state.files[f.id];
const merged = { ...(prev || {}), ...f } as FileMetadata;
if (!prev || !shallowEqualFile(prev, merged)) { newFiles[f.id] = merged; changed = true; }
if (!newOrder.includes(f.id)) { newOrder.unshift(f.id); changed = true; }
});
if (!changed) return {} as any;
return { files: newFiles, order: newOrder };
}),
addFile: (file, opts) => set(state => {
const prepend = opts?.prepend !== false; // default true
if (state.files[file.id]) {
if (shallowEqualFile(state.files[file.id], file)) return {} as any;
return { files: { ...state.files, [file.id]: file } };
}
return {
files: { ...state.files, [file.id]: file },
order: prepend ? [file.id, ...state.order] : [...state.order, file.id],
};
}),
updateFile: (id, patch) => set(state => {
const existing = state.files[id];
if (!existing) return {} as any;
const updated = { ...existing, ...patch } as FileMetadata;
if (shallowEqualFile(existing, updated)) return {} as any;
return { files: { ...state.files, [id]: updated } };
}),
removeFile: (id) => set(state => {
if (!state.files[id]) return {} as any;
const { [id]: _removed, ...rest } = state.files;
const newOrder = state.order.filter(fid => fid !== id);
return { files: rest, order: newOrder };
}),
setUploading: (val) => set({ uploading: val }),
setDeleting: (id) => set({ deleting: id }),
setUploadProgress: (p) => set({ uploadProgress: p }),
reset: () => set(initialState),
}));
// selectors
export const useFiles = () => {
const files = useFileStore(s => s.files);
const order = useFileStore(s => s.order);
// Return stable array when contents unchanged
const out = order.map((id: string) => files[id]);
return out;
};
export const useUploading = () => useFileStore(s => s.uploading);
export const useUploadAggregateProgress = () => useFileStore(s => s.uploadProgress);
export const useDeleting = () => useFileStore(s => s.deleting);