Replace the legacy mule-image backend with PhotoPrism plus a thin SvelteKit client and a Node sidecar for endpoints PhotoPrism doesn't expose (file rename), and add a two-phase migrator (metadata via PUT, heaps → albums) for the existing Postgres library. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
79 lines
2.4 KiB
TypeScript
79 lines
2.4 KiB
TypeScript
/**
|
|
* Drag-to-resize Svelte action. Attaches pointerdown to the host element
|
|
* (a thin handle on the inner edge of a sidebar) and writes the new width
|
|
* back via the supplied setter. The pointer is captured so the drag keeps
|
|
* tracking when the cursor leaves the handle.
|
|
*
|
|
* edge: 'right' — handle on the right edge of the panel; drag right widens
|
|
* edge: 'left' — handle on the left edge of the panel; drag left widens
|
|
*
|
|
* Usage:
|
|
* <div use:resizable={{ edge: 'right', getWidth: () => view.leftSidebarWidth, setWidth: setLeftSidebarWidth }} />
|
|
*/
|
|
export interface ResizableParams {
|
|
edge: 'right' | 'left';
|
|
getWidth: () => number;
|
|
setWidth: (px: number) => void;
|
|
}
|
|
|
|
export function resizable(node: HTMLElement, initial: ResizableParams) {
|
|
let params = initial;
|
|
let pointerId = -1;
|
|
let startX = 0;
|
|
let startWidth = 0;
|
|
|
|
function onDown(e: PointerEvent) {
|
|
if (e.button !== 0) return;
|
|
pointerId = e.pointerId;
|
|
startX = e.clientX;
|
|
startWidth = params.getWidth();
|
|
node.setPointerCapture(pointerId);
|
|
document.body.style.cursor = 'col-resize';
|
|
document.body.style.userSelect = 'none';
|
|
node.addEventListener('pointermove', onMove);
|
|
node.addEventListener('pointerup', onUp);
|
|
node.addEventListener('pointercancel', onUp);
|
|
}
|
|
|
|
function onMove(e: PointerEvent) {
|
|
if (e.pointerId !== pointerId) return;
|
|
const dx = e.clientX - startX;
|
|
const delta = params.edge === 'right' ? dx : -dx;
|
|
params.setWidth(startWidth + delta);
|
|
}
|
|
|
|
function onUp(e: PointerEvent) {
|
|
if (pointerId === -1) return;
|
|
try {
|
|
node.releasePointerCapture(pointerId);
|
|
} catch {
|
|
// Pointer may already be released; ignore.
|
|
}
|
|
pointerId = -1;
|
|
document.body.style.cursor = '';
|
|
document.body.style.userSelect = '';
|
|
node.removeEventListener('pointermove', onMove);
|
|
node.removeEventListener('pointerup', onUp);
|
|
node.removeEventListener('pointercancel', onUp);
|
|
}
|
|
|
|
function onDoubleClick() {
|
|
// Reset to the current default-ish midpoint. Callers can override by
|
|
// providing their own dblclick handler; we just stop pointer events
|
|
// from leaking up so the page underneath doesn't react.
|
|
}
|
|
|
|
node.addEventListener('pointerdown', onDown);
|
|
node.addEventListener('dblclick', onDoubleClick);
|
|
|
|
return {
|
|
update(next: ResizableParams) {
|
|
params = next;
|
|
},
|
|
destroy() {
|
|
node.removeEventListener('pointerdown', onDown);
|
|
node.removeEventListener('dblclick', onDoubleClick);
|
|
}
|
|
};
|
|
}
|