Copy widget in the widget panel or mod+c keeps one widget in localStorage; a paste button in the dock and mod+v insert it below the other widgets with a fresh id. Placement is shared with adding a widget.
54 lines
1.8 KiB
TypeScript
54 lines
1.8 KiB
TypeScript
import { useMemo, useSyncExternalStore } from "react"
|
|
|
|
import type { WidgetDef } from "@/client"
|
|
import { safeStorage } from "@/lib/safeStorage"
|
|
import { WIDGET_LABELS } from "./widgets"
|
|
|
|
/**
|
|
* One copied widget, waiting to be pasted.
|
|
*
|
|
* ponytail: localStorage, as with the flow editor's node clipboard, so a copy
|
|
* survives a reload and reaches every dashboard and tab of this instance. Its
|
|
* message references are flow-qualified, so it pastes anywhere here as-is.
|
|
*/
|
|
const CLIPBOARD_KEY = "fluksio.widgetClipboard"
|
|
|
|
// The `storage` event only reaches the other tabs, so this one is told here.
|
|
const listeners = new Set<() => void>()
|
|
|
|
/** What was stored, if it is still a widget this app draws. */
|
|
export function parseWidget(stored: string | null): WidgetDef | null {
|
|
if (!stored) return null
|
|
try {
|
|
const widget = JSON.parse(stored)
|
|
return Object.keys(WIDGET_LABELS).includes(widget?.type) ? widget : null
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
/** Replace the copy. False when the browser would not store it. */
|
|
export function copyWidget(widget: WidgetDef): boolean {
|
|
const stored = safeStorage.set(CLIPBOARD_KEY, JSON.stringify(widget))
|
|
for (const listener of listeners) listener()
|
|
return stored
|
|
}
|
|
|
|
function subscribe(listener: () => void) {
|
|
listeners.add(listener)
|
|
window.addEventListener("storage", listener)
|
|
return () => {
|
|
listeners.delete(listener)
|
|
window.removeEventListener("storage", listener)
|
|
}
|
|
}
|
|
|
|
/** The copied widget, kept current as any tab copies another. */
|
|
export function useCopiedWidget(): WidgetDef | null {
|
|
// The raw string is the snapshot: it compares by value, a parse would not.
|
|
const stored = useSyncExternalStore(subscribe, () =>
|
|
safeStorage.get(CLIPBOARD_KEY),
|
|
)
|
|
return useMemo(() => parseWidget(stored), [stored])
|
|
}
|