- Foldable left panel (user profile) and right panel (task details) - Clicking a task in the list or graph node selects it and shows details - Both views (task list + graph) always mounted via absolute inset-0 for correct canvas dimensions; tabs toggle visibility with opacity - Graph node selection animation: other nodes repel outward (charge -600), then selected node smoothly slides to center (500ms cubic ease-out), then charge restores to -120 and graph stabilizes - Graph re-fits on tab switch and panel resize via ResizeObserver - Fix UUID string IDs throughout (backend returns UUIDs, not integers) - Add TaskDetailPanel, UserPanel components - Add CLAUDE.md project documentation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
68 lines
1.6 KiB
JavaScript
68 lines
1.6 KiB
JavaScript
import { useState, useLayoutEffect, useEffect } from 'preact/hooks';
|
|
import { is } from './util';
|
|
|
|
/**
|
|
* This is taken from https://github.com/facebook/react/blob/main/packages/use-sync-external-store/src/useSyncExternalStoreShimClient.js#L84
|
|
* on a high level this cuts out the warnings, ... and attempts a smaller implementation
|
|
* @typedef {{ _value: any; _getSnapshot: () => any }} Store
|
|
*/
|
|
export function useSyncExternalStore(subscribe, getSnapshot) {
|
|
const value = getSnapshot();
|
|
|
|
/**
|
|
* @typedef {{ _instance: Store }} StoreRef
|
|
* @type {[StoreRef, (store: StoreRef) => void]}
|
|
*/
|
|
const [{ _instance }, forceUpdate] = useState({
|
|
_instance: { _value: value, _getSnapshot: getSnapshot }
|
|
});
|
|
|
|
useLayoutEffect(() => {
|
|
_instance._value = value;
|
|
_instance._getSnapshot = getSnapshot;
|
|
|
|
if (didSnapshotChange(_instance)) {
|
|
forceUpdate({ _instance });
|
|
}
|
|
}, [subscribe, value, getSnapshot]);
|
|
|
|
useEffect(() => {
|
|
if (didSnapshotChange(_instance)) {
|
|
forceUpdate({ _instance });
|
|
}
|
|
|
|
return subscribe(() => {
|
|
if (didSnapshotChange(_instance)) {
|
|
forceUpdate({ _instance });
|
|
}
|
|
});
|
|
}, [subscribe]);
|
|
|
|
return value;
|
|
}
|
|
|
|
/** @type {(inst: Store) => boolean} */
|
|
function didSnapshotChange(inst) {
|
|
try {
|
|
return !is(inst._value, inst._getSnapshot());
|
|
} catch (error) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export function startTransition(cb) {
|
|
cb();
|
|
}
|
|
|
|
export function useDeferredValue(val) {
|
|
return val;
|
|
}
|
|
|
|
export function useTransition() {
|
|
return [false, startTransition];
|
|
}
|
|
|
|
// TODO: in theory this should be done after a VNode is diffed as we want to insert
|
|
// styles/... before it attaches
|
|
export const useInsertionEffect = useLayoutEffect;
|