Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
48 lines
3.5 KiB
JavaScript
48 lines
3.5 KiB
JavaScript
// The picker wheel: press the middle mouse button anywhere and every offered action fans out under the pointer; release on one to run it.
|
|
import { composer as c } from "../../composer/composer.js";
|
|
import { offer } from "../../shell/actions.js";
|
|
|
|
// Per browser, not per project: the wheel is a habit of the hand. enabled null = everything offered.
|
|
const KEY = "funpack_wheel";
|
|
export function readPrefs() {
|
|
try { const p = JSON.parse(localStorage.getItem(KEY)) || {}; return { shape: p.shape === "half" ? "half" : "full", side: p.side === "left" ? "left" : "right", enabled: Array.isArray(p.enabled) ? p.enabled : null }; }
|
|
catch { return { shape: "full", side: "right", enabled: null }; }
|
|
}
|
|
const writePrefs = (p) => { try { localStorage.setItem(KEY, JSON.stringify(p)); } catch { /* private window: lasts until reload */ } };
|
|
|
|
export default {
|
|
id: "wheel",
|
|
mount: "menubar.menus",
|
|
needs: ["project"],
|
|
setup({ app }) {
|
|
let wheel = null;
|
|
const offs = [
|
|
offer(app, { id: "undo", label: "Undo", icon: "↶", run: () => app.project.undo() }),
|
|
offer(app, { id: "redo", label: "Redo", icon: "↷", run: () => app.project.redo() }),
|
|
offer(app, { id: "models", label: "Models & Pipeline", icon: "▤", when: () => Boolean(app.openSettings), run: () => app.openSettings("models") }),
|
|
offer(app, { id: "engine", label: "Engine settings", icon: "⚙", when: () => Boolean(app.openSettings), run: () => app.openSettings("engine") }),
|
|
];
|
|
const onDown = (e) => {
|
|
if (e.button !== 1 || wheel || document.querySelector('[role="dialog"]')) return; // nothing behind a dialog
|
|
e.preventDefault();
|
|
const prefs = readPrefs(), on = (a) => !prefs.enabled || prefs.enabled.includes(a.id);
|
|
const items = (app.actions || []).filter((a) => on(a) && (!a.when || a.when())).slice(0, 12).map((a) => ({ id: a.id, label: a.label, icon: a.icon }));
|
|
if (!items.length) return;
|
|
const shared = { items, onClose: () => { wheel = null; },
|
|
onPick: (item) => { const a = (app.actions || []).find((x) => x.id === item.id); if (a) Promise.resolve().then(() => a.run()).catch((err) => c.toast.warn({ text: err.message })); } };
|
|
wheel = prefs.shape === "half" ? c.wheel.half({ ...shared, edge: prefs.side, at: e.clientY }) : c.wheel.picker({ ...shared, x: e.clientX, y: e.clientY });
|
|
};
|
|
app.editorSettings.push(() => {
|
|
const prefs = readPrefs(), set = (patch) => writePrefs({ ...readPrefs(), ...patch });
|
|
const offered = (app.actions || []).map((a) => ({ value: a.id, label: `${a.icon || ""} ${a.label}`.trim() }));
|
|
return c.region.stack({ gap: "sm", children: [c.label.section({ text: "Picker wheel" }),
|
|
c.hint.default({ text: "Hold the middle mouse button anywhere, flick toward an action and let go." }),
|
|
c.segmented.md({ label: "Shape", value: prefs.shape, onChange: (v) => set({ shape: v }), options: [{ value: "full", label: "Full wheel" }, { value: "half", label: "Half wheel" }] }),
|
|
c.segmented.md({ label: "A half wheel opens", value: prefs.side, onChange: (v) => set({ side: v }), options: [{ value: "left", label: "Left" }, { value: "right", label: "Right" }] }),
|
|
c.checklist.default({ label: "On the wheel", items: offered, values: prefs.enabled || offered.map((o) => o.value), onChange: (v) => set({ enabled: v }) })] });
|
|
});
|
|
document.addEventListener("mousedown", onDown, true);
|
|
return () => { document.removeEventListener("mousedown", onDown, true); if (wheel) wheel.close && wheel.close(); offs.forEach((f) => f()); };
|
|
},
|
|
};
|