mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-16 03:42:20 +02:00
Photoshop-parity editor client: layers, selection/masking, brushes,
smart filters, LUT import, Quick Edit, mobile gestures, workspaces,
batch operations, and the service-worker versioning (v978->v1006).
Source: vendor/photocraft (ad1cff44) photo-editor client files, merged
onto upstream/dev (a8c147b2). Conflict resolution documented in
pr_work/A_conflicts.md; editor files taken from vendor/photocraft
(upstream's editor is an earlier snapshot of the same feature).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
98 lines
2.9 KiB
JavaScript
98 lines
2.9 KiB
JavaScript
// Programmatic control surface for the PhotoCraft editor.
|
|
//
|
|
// This module turns the editor's internal operations into a stable,
|
|
// JSON-friendly command API so an MCP server (or the in-editor natural
|
|
// language entrypoint) can drive the editor without poking at private
|
|
// closures. The heavy lifting lives in galleryEditor.js, which builds the
|
|
// handler map from its in-scope functions and installs it on
|
|
// window.geEditorControl.
|
|
//
|
|
// Contract:
|
|
// control.invoke(op, params) -> Promise<{ ok, result } | { ok:false, error }>
|
|
// control.run(steps) -> Promise<Array<stepResult>> (sequential)
|
|
// control.list() -> [{ op, description }]
|
|
// control.describe(op) -> { op, description } | null
|
|
//
|
|
// Every op name is namespaced: document.*, layers.*, selection.*,
|
|
// adjustments.*, filters.*, transform.*, text.*, history.*, export.*.
|
|
|
|
export const EDITOR_CONTROL_VERSION = '1.0';
|
|
|
|
function normalizeError(err) {
|
|
if (!err) return 'Unknown error';
|
|
if (typeof err === 'string') return err;
|
|
return err.message || String(err);
|
|
}
|
|
|
|
export function createEditorControl({ handlers = {}, getState = null, version = EDITOR_CONTROL_VERSION } = {}) {
|
|
const registry = new Map();
|
|
for (const [op, entry] of Object.entries(handlers)) {
|
|
if (!entry) continue;
|
|
if (typeof entry === 'function') {
|
|
registry.set(op, { run: entry, description: '' });
|
|
} else if (typeof entry.run === 'function') {
|
|
registry.set(op, { run: entry.run, description: entry.description || '' });
|
|
}
|
|
}
|
|
|
|
function describe(op) {
|
|
const entry = registry.get(op);
|
|
if (!entry) return null;
|
|
return { op, description: entry.description };
|
|
}
|
|
|
|
function list() {
|
|
return Array.from(registry.keys())
|
|
.sort()
|
|
.map((op) => ({ op, description: registry.get(op).description }));
|
|
}
|
|
|
|
function snapshot() {
|
|
if (typeof getState !== 'function') return {};
|
|
try {
|
|
return getState() || {};
|
|
} catch (_err) {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
async function invoke(op, params = {}) {
|
|
const entry = registry.get(op);
|
|
if (!entry) {
|
|
return { ok: false, error: `Unknown editor op: ${op}`, op };
|
|
}
|
|
try {
|
|
const result = await entry.run(params || {}, { state: snapshot(), control });
|
|
return { ok: true, op, result: result === undefined ? null : result };
|
|
} catch (err) {
|
|
return { ok: false, op, error: normalizeError(err) };
|
|
}
|
|
}
|
|
|
|
async function run(steps) {
|
|
const list = Array.isArray(steps) ? steps : [];
|
|
const results = [];
|
|
for (const step of list) {
|
|
const op = step && step.op;
|
|
const params = (step && step.params) || {};
|
|
// eslint-disable-next-line no-await-in-loop
|
|
const res = await invoke(op, params);
|
|
results.push(res);
|
|
if (!res.ok) break;
|
|
}
|
|
return results;
|
|
}
|
|
|
|
const control = {
|
|
version,
|
|
installed: true,
|
|
list,
|
|
describe,
|
|
invoke,
|
|
run,
|
|
};
|
|
return control;
|
|
}
|
|
|
|
export default createEditorControl;
|