@pixerate/editor 0.144.0 → 0.146.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/animation/index.cjs +2 -2
- package/dist/animation/index.js +1 -1
- package/dist/animation/player.d.cts +5 -0
- package/dist/animation/player.d.ts +5 -0
- package/dist/animation-editor/controller.d.cts +101 -1
- package/dist/animation-editor/controller.d.ts +101 -1
- package/dist/animation-editor/index.cjs +594 -147
- package/dist/animation-editor/index.d.cts +1 -0
- package/dist/animation-editor/index.d.ts +1 -0
- package/dist/animation-editor/index.js +525 -78
- package/dist/animation-editor/record.d.cts +114 -0
- package/dist/animation-editor/record.d.ts +114 -0
- package/dist/animation-editor/tools.d.cts +1 -0
- package/dist/animation-editor/tools.d.ts +1 -0
- package/dist/animation-editor/types.d.cts +6 -0
- package/dist/animation-editor/types.d.ts +6 -0
- package/dist/canvas/index.cjs +24 -24
- package/dist/canvas/index.js +1 -1
- package/dist/chunk-HRLT5BYO.js +133 -0
- package/dist/chunk-KC7HB5GF.cjs +133 -0
- package/dist/{chunk-I4T4J27S.js → chunk-QX25HWFL.js} +9 -0
- package/dist/{chunk-SVR5FR7B.cjs → chunk-UUTTJK77.cjs} +9 -0
- package/dist/diagram/editorDocument.d.cts +12 -0
- package/dist/diagram/editorDocument.d.ts +12 -0
- package/dist/diagram/index.cjs +35 -1
- package/dist/diagram/index.d.cts +1 -0
- package/dist/diagram/index.d.ts +1 -0
- package/dist/diagram/index.js +34 -0
- package/dist/persistence/documentStore.d.cts +51 -0
- package/dist/persistence/documentStore.d.ts +51 -0
- package/dist/persistence/documentTools.d.cts +35 -0
- package/dist/persistence/documentTools.d.ts +35 -0
- package/dist/persistence/editorAutosave.d.cts +33 -0
- package/dist/persistence/editorAutosave.d.ts +33 -0
- package/dist/persistence/editorDocument.d.cts +118 -0
- package/dist/persistence/editorDocument.d.ts +118 -0
- package/dist/persistence/index.cjs +294 -1
- package/dist/persistence/index.d.cts +4 -0
- package/dist/persistence/index.d.ts +4 -0
- package/dist/persistence/index.js +293 -0
- package/dist/video-editor/index.cjs +2 -2
- package/dist/video-editor/index.js +1 -1
- package/package.json +1 -1
- package/skills/pixerate-agent-tools/SKILL.md +21 -0
- package/skills/pixerate-animation-editor/SKILL.md +10 -0
- package/skills/pixerate-animation-editor/references/commands.md +3 -1
- package/skills/pixerate-diagram/SKILL.md +13 -0
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EDITOR_DOCUMENT_FORMAT,
|
|
3
|
+
EditorDocumentLoadError,
|
|
4
|
+
defineEditorDocument,
|
|
5
|
+
isEditorDocumentFile,
|
|
6
|
+
parseEditorDocument,
|
|
7
|
+
serializeEditorDocument,
|
|
8
|
+
toEditorDocumentFile
|
|
9
|
+
} from "../chunk-HRLT5BYO.js";
|
|
1
10
|
import {
|
|
2
11
|
createLoadReport,
|
|
3
12
|
hasLoadProblems,
|
|
@@ -651,10 +660,288 @@ function createRevisionTracker(options = {}) {
|
|
|
651
660
|
}
|
|
652
661
|
};
|
|
653
662
|
}
|
|
663
|
+
|
|
664
|
+
// src/persistence/editorAutosave.ts
|
|
665
|
+
function createEditorAutosave(doc, options) {
|
|
666
|
+
const { dirty, key, ...rest } = options;
|
|
667
|
+
const revisions = createRevisionTracker({ history: doc, dirty });
|
|
668
|
+
const controller = createAutosaveController({
|
|
669
|
+
...rest,
|
|
670
|
+
key: key ?? doc.kind,
|
|
671
|
+
getSnapshot: () => toEditorDocumentFile(doc),
|
|
672
|
+
deserialize: (text) => parseEditorDocument(text, { kind: doc.kind }),
|
|
673
|
+
revision: revisions.revision,
|
|
674
|
+
savedRevision: revisions.savedRevision
|
|
675
|
+
});
|
|
676
|
+
const unsubscribe = revisions.subscribe(() => controller.notify());
|
|
677
|
+
return {
|
|
678
|
+
controller,
|
|
679
|
+
revisions,
|
|
680
|
+
checkRecovery: () => controller.checkRecovery(),
|
|
681
|
+
restore(recovery) {
|
|
682
|
+
if (!recovery.snapshot) throw recovery.error ?? new Error("The recovered snapshot could not be read.");
|
|
683
|
+
const report = doc.loadDocument(recovery.snapshot, { history: "reset" });
|
|
684
|
+
controller.ready();
|
|
685
|
+
return report;
|
|
686
|
+
},
|
|
687
|
+
async discard() {
|
|
688
|
+
await controller.discardRecovery();
|
|
689
|
+
controller.ready();
|
|
690
|
+
},
|
|
691
|
+
async markClean() {
|
|
692
|
+
revisions.markSaved();
|
|
693
|
+
await controller.markClean();
|
|
694
|
+
},
|
|
695
|
+
destroy() {
|
|
696
|
+
unsubscribe();
|
|
697
|
+
revisions.destroy();
|
|
698
|
+
controller.destroy();
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// src/persistence/documentStore.ts
|
|
704
|
+
function slugifyDocumentName(name) {
|
|
705
|
+
const slug = name.normalize("NFKD").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
706
|
+
return slug || "untitled";
|
|
707
|
+
}
|
|
708
|
+
function createEditorDocumentStore(options) {
|
|
709
|
+
const { storage } = options;
|
|
710
|
+
const ns = options.namespace ?? "pixerate.documents";
|
|
711
|
+
const now = options.now ?? Date.now;
|
|
712
|
+
const indexKey = `${ns}:index`;
|
|
713
|
+
const fileKey = (key) => `${ns}:file:${key}`;
|
|
714
|
+
let index = null;
|
|
715
|
+
let queue = Promise.resolve();
|
|
716
|
+
const run = (task) => {
|
|
717
|
+
const result = queue.then(task, task);
|
|
718
|
+
queue = result.catch(() => void 0);
|
|
719
|
+
return result;
|
|
720
|
+
};
|
|
721
|
+
const loadIndex = async () => {
|
|
722
|
+
if (index) return index;
|
|
723
|
+
const raw = await storage.get(indexKey);
|
|
724
|
+
let items = [];
|
|
725
|
+
if (raw) {
|
|
726
|
+
try {
|
|
727
|
+
const data = JSON.parse(raw);
|
|
728
|
+
if (data && Array.isArray(data.items)) {
|
|
729
|
+
items = data.items.filter((item) => item && typeof item.key === "string" && typeof item.kind === "string");
|
|
730
|
+
}
|
|
731
|
+
} catch {
|
|
732
|
+
items = [];
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
index = items;
|
|
736
|
+
return index;
|
|
737
|
+
};
|
|
738
|
+
const saveIndex = async (items) => {
|
|
739
|
+
await storage.set(indexKey, JSON.stringify({ version: 1, items }));
|
|
740
|
+
index = items;
|
|
741
|
+
};
|
|
742
|
+
const newestFirst = (a, b) => a.savedAt < b.savedAt ? 1 : a.savedAt > b.savedAt ? -1 : 0;
|
|
743
|
+
const readFile = async (key) => {
|
|
744
|
+
const raw = await storage.get(fileKey(key));
|
|
745
|
+
return raw === null ? null : parseEditorDocument(raw);
|
|
746
|
+
};
|
|
747
|
+
return {
|
|
748
|
+
save: (doc, name) => run(async () => {
|
|
749
|
+
const trimmed = name.trim() || "Untitled";
|
|
750
|
+
const entry = {
|
|
751
|
+
key: `${doc.kind}/${slugifyDocumentName(trimmed)}`,
|
|
752
|
+
kind: doc.kind,
|
|
753
|
+
name: trimmed,
|
|
754
|
+
formatVersion: doc.formatVersion,
|
|
755
|
+
savedAt: new Date(now()).toISOString()
|
|
756
|
+
};
|
|
757
|
+
await storage.set(fileKey(entry.key), JSON.stringify(toEditorDocumentFile(doc, trimmed)));
|
|
758
|
+
await saveIndex([...(await loadIndex()).filter((item) => item.key !== entry.key), entry]);
|
|
759
|
+
return entry;
|
|
760
|
+
}),
|
|
761
|
+
list: (kind) => run(async () => (await loadIndex()).filter((item) => !kind || item.kind === kind).sort(newestFirst)),
|
|
762
|
+
read: (key) => run(() => readFile(key)),
|
|
763
|
+
open: (doc, key, openOptions = {}) => run(async () => {
|
|
764
|
+
const entry = (await loadIndex()).find((item) => item.key === key);
|
|
765
|
+
const file = entry ? await readFile(key) : null;
|
|
766
|
+
if (!entry || !file) {
|
|
767
|
+
const keys = (await loadIndex()).filter((item) => item.kind === doc.kind).sort(newestFirst).map((item) => item.key);
|
|
768
|
+
throw new EditorDocumentLoadError(
|
|
769
|
+
`There is no saved document "${key}". ${keys.length ? `Saved ${doc.kind} documents: ${keys.join(", ")}.` : `No ${doc.kind} documents are saved yet.`}`
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
const report = doc.loadDocument(file, { history: openOptions.history ?? "reset" });
|
|
773
|
+
return { ...entry, report };
|
|
774
|
+
}),
|
|
775
|
+
remove: (key) => run(async () => {
|
|
776
|
+
const items = await loadIndex();
|
|
777
|
+
if (!items.some((item) => item.key === key)) return false;
|
|
778
|
+
await saveIndex(items.filter((item) => item.key !== key));
|
|
779
|
+
await storage.remove(fileKey(key));
|
|
780
|
+
return true;
|
|
781
|
+
})
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// src/persistence/documentTools.ts
|
|
786
|
+
var READ = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
787
|
+
var WRITE = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
|
|
788
|
+
var REPLACE = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false };
|
|
789
|
+
var OVERWRITE = { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false };
|
|
790
|
+
var object = (properties, required = []) => ({
|
|
791
|
+
type: "object",
|
|
792
|
+
properties,
|
|
793
|
+
...required.length ? { required } : {},
|
|
794
|
+
additionalProperties: false
|
|
795
|
+
});
|
|
796
|
+
var reportOut = (report) => ({
|
|
797
|
+
...report.migratedFrom !== null ? { migratedFrom: report.migratedFrom } : {},
|
|
798
|
+
...report.problems.length ? { problems: report.problems } : {},
|
|
799
|
+
...report.unknownKeys.length ? { unknownKeys: report.unknownKeys } : {}
|
|
800
|
+
});
|
|
801
|
+
function createDocumentToolProvider(doc, options = {}) {
|
|
802
|
+
const { kind } = doc;
|
|
803
|
+
const noun = options.noun ?? kind.replace(/_/g, " ");
|
|
804
|
+
const maxChars = options.maxChars ?? 2e5;
|
|
805
|
+
const store = options.store;
|
|
806
|
+
const writable = () => {
|
|
807
|
+
const reason = doc.readOnly();
|
|
808
|
+
if (reason) throw new Error(`${reason} Read tools still work; write tools are refused.`);
|
|
809
|
+
};
|
|
810
|
+
const historyOut = () => {
|
|
811
|
+
const h = doc.history.getState();
|
|
812
|
+
return {
|
|
813
|
+
revision: doc.revision(),
|
|
814
|
+
canUndo: h.canUndo,
|
|
815
|
+
canRedo: h.canRedo,
|
|
816
|
+
...h.undoName ? { undoName: h.undoName } : {},
|
|
817
|
+
...h.redoName ? { redoName: h.redoName } : {}
|
|
818
|
+
};
|
|
819
|
+
};
|
|
820
|
+
const loaded = (report) => ({
|
|
821
|
+
revision: doc.revision(),
|
|
822
|
+
undoable: doc.recordsLoads,
|
|
823
|
+
...reportOut(report)
|
|
824
|
+
});
|
|
825
|
+
const get = {
|
|
826
|
+
name: `get_${kind}_document`,
|
|
827
|
+
title: `Get the ${noun} document`,
|
|
828
|
+
description: `Returns the whole ${noun} as plain JSON: { kind, formatVersion, revision, document }. Use it to keep a copy, compare versions or pass the document to load_${kind}_document later. For reading or editing parts of the ${noun}, prefer the editor's own read tools: this result can be large.`,
|
|
829
|
+
inputSchema: object({}),
|
|
830
|
+
annotations: { title: `Get the ${noun} document`, ...READ },
|
|
831
|
+
handler: () => {
|
|
832
|
+
const document = doc.getDocument();
|
|
833
|
+
const size = JSON.stringify(document)?.length ?? 0;
|
|
834
|
+
if (size > maxChars) {
|
|
835
|
+
throw new Error(`The ${noun} document is ${size} characters of JSON, over the ${maxChars} limit for this tool. Use the editor's read tools to look at parts of it.`);
|
|
836
|
+
}
|
|
837
|
+
return { kind, formatVersion: doc.formatVersion, revision: doc.revision(), document };
|
|
838
|
+
}
|
|
839
|
+
};
|
|
840
|
+
const load = {
|
|
841
|
+
name: `load_${kind}_document`,
|
|
842
|
+
title: `Load a ${noun} document`,
|
|
843
|
+
description: `Replaces the whole ${noun} with a document from get_${kind}_document (its "document" value, or the whole result as a saved file). The document is checked first: when it does not fit, nothing changes and the error says why. Returns { revision, undoable, migratedFrom?, problems?, unknownKeys? }; undoable: true means undo_${kind} brings the previous ${noun} back.`,
|
|
844
|
+
inputSchema: object(
|
|
845
|
+
{
|
|
846
|
+
document: {
|
|
847
|
+
type: "object",
|
|
848
|
+
description: `The ${noun} document: the "document" value from get_${kind}_document, or a saved file ({ format, kind, formatVersion, document }).`
|
|
849
|
+
}
|
|
850
|
+
},
|
|
851
|
+
["document"]
|
|
852
|
+
),
|
|
853
|
+
annotations: { title: `Load a ${noun} document`, ...REPLACE },
|
|
854
|
+
handler: ({ document }) => {
|
|
855
|
+
writable();
|
|
856
|
+
return loaded(doc.loadDocument(document, { history: "record" }));
|
|
857
|
+
}
|
|
858
|
+
};
|
|
859
|
+
const undo = {
|
|
860
|
+
name: `undo_${kind}`,
|
|
861
|
+
title: `Undo a ${noun} edit`,
|
|
862
|
+
description: `Undoes the last ${noun} edit (one tool call or one user gesture). Returns { undone, revision, canUndo, canRedo, undoName?, redoName? }; undone is the name of the step that was undone.`,
|
|
863
|
+
inputSchema: object({}),
|
|
864
|
+
annotations: { title: `Undo a ${noun} edit`, ...WRITE },
|
|
865
|
+
handler: () => {
|
|
866
|
+
writable();
|
|
867
|
+
const name = doc.history.getState().undoName;
|
|
868
|
+
if (!doc.history.getState().canUndo || !doc.history.undo()) throw new Error("Nothing to undo.");
|
|
869
|
+
return { undone: name ?? true, ...historyOut() };
|
|
870
|
+
}
|
|
871
|
+
};
|
|
872
|
+
const redo = {
|
|
873
|
+
name: `redo_${kind}`,
|
|
874
|
+
title: `Redo a ${noun} edit`,
|
|
875
|
+
description: `Redoes the last undone ${noun} edit. Returns { redone, revision, canUndo, canRedo, undoName?, redoName? }; redone is the name of the step that was redone.`,
|
|
876
|
+
inputSchema: object({}),
|
|
877
|
+
annotations: { title: `Redo a ${noun} edit`, ...WRITE },
|
|
878
|
+
handler: () => {
|
|
879
|
+
writable();
|
|
880
|
+
const name = doc.history.getState().redoName;
|
|
881
|
+
if (!doc.history.getState().canRedo || !doc.history.redo()) throw new Error("Nothing to redo.");
|
|
882
|
+
return { redone: name ?? true, ...historyOut() };
|
|
883
|
+
}
|
|
884
|
+
};
|
|
885
|
+
const storeTools = (s) => [
|
|
886
|
+
{
|
|
887
|
+
name: `save_${kind}_document`,
|
|
888
|
+
title: `Save the ${noun}`,
|
|
889
|
+
description: `Saves the current ${noun} under a name in the host's document storage. Saving the same name again replaces that saved copy. Returns { key, name, savedAt, revision }; pass key to open_saved_${kind}_document to load it again.`,
|
|
890
|
+
inputSchema: object({
|
|
891
|
+
name: {
|
|
892
|
+
type: "string",
|
|
893
|
+
minLength: 1,
|
|
894
|
+
maxLength: 200,
|
|
895
|
+
description: `Name to save under (default: the ${noun}'s current name, or "Untitled ${noun}").`
|
|
896
|
+
}
|
|
897
|
+
}),
|
|
898
|
+
annotations: { title: `Save the ${noun}`, ...OVERWRITE },
|
|
899
|
+
handler: async ({ name }) => {
|
|
900
|
+
const entry = await s.save(doc, name ?? options.name?.() ?? `Untitled ${noun}`);
|
|
901
|
+
return { key: entry.key, name: entry.name, savedAt: entry.savedAt, revision: doc.revision() };
|
|
902
|
+
}
|
|
903
|
+
},
|
|
904
|
+
{
|
|
905
|
+
name: `list_saved_${kind}_documents`,
|
|
906
|
+
title: `List saved ${noun} documents`,
|
|
907
|
+
description: `Lists the ${noun} documents in the host's document storage, newest first. Returns { documents: [{ key, name, savedAt, formatVersion }] }.`,
|
|
908
|
+
inputSchema: object({}),
|
|
909
|
+
annotations: { title: `List saved ${noun} documents`, ...READ },
|
|
910
|
+
handler: async () => ({
|
|
911
|
+
documents: (await s.list(kind)).map(({ key, name, savedAt, formatVersion }) => ({ key, name, savedAt, formatVersion }))
|
|
912
|
+
})
|
|
913
|
+
},
|
|
914
|
+
{
|
|
915
|
+
name: `open_saved_${kind}_document`,
|
|
916
|
+
title: `Open a saved ${noun}`,
|
|
917
|
+
description: `Replaces the current ${noun} with a saved one. Unsaved changes stay reachable through undo_${kind} when undoable is true. Returns { key, name, revision, undoable, migratedFrom?, problems?, unknownKeys? }.`,
|
|
918
|
+
inputSchema: object({ key: { type: "string", minLength: 1, description: `A key from list_saved_${kind}_documents or save_${kind}_document.` } }, ["key"]),
|
|
919
|
+
annotations: { title: `Open a saved ${noun}`, ...REPLACE },
|
|
920
|
+
handler: async ({ key }) => {
|
|
921
|
+
writable();
|
|
922
|
+
const entry = await s.open(doc, key, { history: "record" });
|
|
923
|
+
return { key: entry.key, name: entry.name, ...loaded(entry.report) };
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
];
|
|
927
|
+
return {
|
|
928
|
+
id: options.id ?? `${kind}-document`,
|
|
929
|
+
...options.namespace !== void 0 ? { namespace: options.namespace } : {},
|
|
930
|
+
title: options.title ?? `${noun.charAt(0).toUpperCase()}${noun.slice(1)} document`,
|
|
931
|
+
description: `Get, load${options.history === false ? "" : ", undo, redo"}${store ? ", save and open" : ""} the whole ${noun}.`,
|
|
932
|
+
...options.skill ? { skill: options.skill } : {},
|
|
933
|
+
tools: [get, load, ...options.history === false ? [] : [undo, redo], ...store ? storeTools(store) : []]
|
|
934
|
+
};
|
|
935
|
+
}
|
|
654
936
|
export {
|
|
937
|
+
EDITOR_DOCUMENT_FORMAT,
|
|
938
|
+
EditorDocumentLoadError,
|
|
655
939
|
InjectedStorageError,
|
|
656
940
|
StorageUnavailableError,
|
|
657
941
|
createAutosaveController,
|
|
942
|
+
createDocumentToolProvider,
|
|
943
|
+
createEditorAutosave,
|
|
944
|
+
createEditorDocumentStore,
|
|
658
945
|
createIndexedDbAdapter,
|
|
659
946
|
createLoadReport,
|
|
660
947
|
createLocalStorageAdapter,
|
|
@@ -662,15 +949,21 @@ export {
|
|
|
662
949
|
createOpfsAdapter,
|
|
663
950
|
createRecoveryPolicy,
|
|
664
951
|
createRevisionTracker,
|
|
952
|
+
defineEditorDocument,
|
|
665
953
|
exponentialBackoff,
|
|
666
954
|
formatRecoveryMessage,
|
|
667
955
|
hasLoadProblems,
|
|
956
|
+
isEditorDocumentFile,
|
|
668
957
|
isIndexedDbAvailable,
|
|
669
958
|
isLocalStorageAvailable,
|
|
670
959
|
isOpfsAvailable,
|
|
671
960
|
loadDocument,
|
|
672
961
|
loadSections,
|
|
673
962
|
migrateDocument,
|
|
963
|
+
parseEditorDocument,
|
|
674
964
|
recoveryMetaKey,
|
|
965
|
+
serializeEditorDocument,
|
|
966
|
+
slugifyDocumentName,
|
|
967
|
+
toEditorDocumentFile,
|
|
675
968
|
withStorageFailures
|
|
676
969
|
};
|
|
@@ -150,7 +150,7 @@ require('../chunk-WOFXZ3XU.cjs');
|
|
|
150
150
|
var _chunkFJ2MSI43cjs = require('../chunk-FJ2MSI43.cjs');
|
|
151
151
|
|
|
152
152
|
|
|
153
|
-
var
|
|
153
|
+
var _chunkUUTTJK77cjs = require('../chunk-UUTTJK77.cjs');
|
|
154
154
|
require('../chunk-DWJJI6RT.cjs');
|
|
155
155
|
require('../chunk-5FXOR7F2.cjs');
|
|
156
156
|
require('../chunk-A6QWKBJP.cjs');
|
|
@@ -2807,7 +2807,7 @@ var VideoEditorController = (_class6 = class {
|
|
|
2807
2807
|
this.readOnly = _nullishCoalesce(options.readOnly, () => ( false));
|
|
2808
2808
|
this.previewScale = checkPreviewScale(_nullishCoalesce(options.previewScale, () => ( "auto")));
|
|
2809
2809
|
this.onChange = options.onChange;
|
|
2810
|
-
this.player = new (0,
|
|
2810
|
+
this.player = new (0, _chunkUUTTJK77cjs.AnimationPlayer)({ clock: options.clock, duration: () => _chunkVKF3PWKRcjs.videoProjectDuration.call(void 0, this.project), loop: "none" });
|
|
2811
2811
|
this.state = this.computeState();
|
|
2812
2812
|
this.connect();
|
|
2813
2813
|
}
|
package/package.json
CHANGED
|
@@ -61,6 +61,27 @@ editor's, or (in discovery mode) only `list_toolsets`, `search_tools`, `enable_t
|
|
|
61
61
|
6. **Undo your own mistakes** with the domain's `undo_*` tool or the `history.undo` command, not
|
|
62
62
|
by hand-reverting with more edits.
|
|
63
63
|
|
|
64
|
+
## Saving, loading and undo
|
|
65
|
+
|
|
66
|
+
Every editor can share the same document tools, named after its kind (`diagram`, `chart`,
|
|
67
|
+
`spreadsheet`...). The editor's skill lists the ones it has.
|
|
68
|
+
|
|
69
|
+
- `get_<kind>_document` returns the whole document as plain JSON
|
|
70
|
+
(`{ kind, formatVersion, revision, document }`). Use it to keep a copy before a risky change
|
|
71
|
+
or to hand the document on, not to read it: the editor's own read tools are smaller and more
|
|
72
|
+
precise. It refuses very large documents and says so.
|
|
73
|
+
- `load_<kind>_document` puts such a copy back. The document is checked first and nothing
|
|
74
|
+
changes when it does not fit. It is one undo step when the result says `undoable: true`.
|
|
75
|
+
- `undo_<kind>` / `redo_<kind>` step through the editor's history. They fail with
|
|
76
|
+
"Nothing to undo." / "Nothing to redo." when there is nothing to step over.
|
|
77
|
+
- With host storage: `save_<kind>_document` (by `name`; the same name replaces that copy),
|
|
78
|
+
`list_saved_<kind>_documents`, then `open_saved_<kind>_document` with a `key` from either.
|
|
79
|
+
Save when the user asks. Opening replaces the current document, so ask first when it has
|
|
80
|
+
changes the user has not saved.
|
|
81
|
+
|
|
82
|
+
A read-only document keeps its read tools; its write tools fail with the reason. `revision`
|
|
83
|
+
goes up on every change, so comparing two results tells you whether the document changed.
|
|
84
|
+
|
|
64
85
|
## Playback
|
|
65
86
|
|
|
66
87
|
The host may act your calls out on screen (agent playback): your cursor glides to what a call
|
|
@@ -36,6 +36,9 @@ The full command vocabulary (every type, option and default) is in
|
|
|
36
36
|
`move_command`, `delete_commands`. Use `write_animation` only to replace every shot.
|
|
37
37
|
For a command type you have not written before, `describe_animation_command` returns its
|
|
38
38
|
exact fields first.
|
|
39
|
+
For motion that should feel performed rather than keyed (a hand-drawn swoop, a jittery
|
|
40
|
+
drag, a colour picked mid-move), `record_animation_take` with timed samples records it as
|
|
41
|
+
one shot, the way a person records live on the stage.
|
|
39
42
|
4. **Fit and cue.** `fit_animation` to hit a length (keep shots whose pace matters). For music
|
|
40
43
|
or voiceover, `set_animation_settings` with `beat` or `narration`, read cues with
|
|
41
44
|
`get_timing_grid`, and give shots `"at": { "beat": n }` or `{ "word": i }`.
|
|
@@ -68,6 +71,12 @@ The full command vocabulary (every type, option and default) is in
|
|
|
68
71
|
`"with": "previous"` plus a small `delay` so it trails the lead.
|
|
69
72
|
- **Arcs.** Natural movement curves: use `move` with `via` waypoints (one smooth path) instead
|
|
70
73
|
of chained moves that stop at each point, or `follow` a path node.
|
|
74
|
+
- **Recorded takes.** Sample a performance at 30-60 per second; the recorder keeps only the
|
|
75
|
+
points that matter (raise `tolerance` for fewer, cleaner commands). Begin each moving
|
|
76
|
+
element with a sample at its current pose (`get_motion_path`'s last point, or its bounds'
|
|
77
|
+
centre at rest) or it jumps there at its first sample. A take is a new shot after the others
|
|
78
|
+
(or at `at`); it cannot be layered over an existing shot. Refine it like any shot: it is
|
|
79
|
+
ordinary `move` (with `via` and `times`), `scale`, `rotate`, `fade` and `style` commands.
|
|
71
80
|
- **Hold long enough to read.** Leave each result on screen (shot `hold`, default 600 ms);
|
|
72
81
|
roughly 250 ms per word for text that must be read, longer for the final frame or logo.
|
|
73
82
|
- **Exaggeration with restraint.** Emphasis (`pulse`, `shake`, `teeter`, `color-pulse`) is for
|
|
@@ -111,6 +120,7 @@ The full command vocabulary (every type, option and default) is in
|
|
|
111
120
|
| `set_animation_drivers` | Replace all drivers (empty list clears): a target's `x`, `y`, `rotation`, `scaleX`, `scaleY` or `opacity` follows a source node's property as change · `factor` + `offset`, optionally `clamp`ed. For parallax, wheels that turn as they roll, shadows that fade as something rises. |
|
|
112
121
|
| `set_animation_clip` | Define, replace or (with `clip: null`) delete a named reusable clip whose `run` targets `"$"`; play it with a `play` command on any nodes. Prefer a clip over repeated identical commands. |
|
|
113
122
|
| `copy_animation` | Give the nodes in `to` every command that animates `from` (all shots or `shots`), each copy starting with its original, optionally `stagger`ed (ms). Quick for one-off duplication; use a clip for motion you will reuse. |
|
|
123
|
+
| `record_animation_take` | Record performed motion as a new shot (one undo step): `samples` of `{ node, t (ms into the take), x, y (centre), scaleX, scaleY, rotation, opacity, fill, stroke, strokeWidth }` become a `move` through waypoints at the recorded `times` and a few eased `scale` / `rotate` / `fade` / `style` commands, each starting when it happened. Optional `length`, `id`, `label`, `at`, `tolerance`; `dry_run: true` returns the run first. Returns the shot id, its start and each command's index, type, start and end; `check_animation` and `render_animation_frame` after. |
|
|
114
124
|
| `fit_animation` | Make the scene last `seconds` by setting shot `speed`, keeping proportions; shots in `keep` play as written. `dry_run: true` reports the new length and speeds first. |
|
|
115
125
|
| `import_flair_effect` | Replace the whole scene with a Flair vector `effect` or SvgMotion `motion` (give exactly one; optional `name`). Placeholders become colour controls; each Flair step becomes a shot. Use `dry_run: true` first. |
|
|
116
126
|
| `insert_shots` | Add one or more complete shots (`id`, `run`, optional `label`, `camera`, `hold`, `speed`, `at`) at position `at` (default the end). Returns every shot's timing and issues. |
|
|
@@ -56,7 +56,9 @@ camera move 1000.
|
|
|
56
56
|
|
|
57
57
|
- `move`: `by` { x, y } offset or `to` { x, y } for the centre. `via`: up to 64 waypoints the
|
|
58
58
|
centre passes through smoothly without overshoot; `pacing` `even` (default, steady speed)
|
|
59
|
-
or `equal` (same time per stretch). `
|
|
59
|
+
or `equal` (same time per stretch). `times`: one rising fraction of the duration (0-1) per
|
|
60
|
+
waypoint, when the centre passes it; overrides `pacing` (recorded takes set it).
|
|
61
|
+
`blend` default replace.
|
|
60
62
|
- `rotate`: `by` degrees or `to` degrees from rest. `blend` default replace.
|
|
61
63
|
- `scale`: `to` a factor or { x, y } (required); `geometric: true` for an even-rate zoom.
|
|
62
64
|
- `fade`: `to` opacity 0-1 (required).
|
|
@@ -111,6 +111,19 @@ text-only families.
|
|
|
111
111
|
| `undo_diagram` | Undo the last diagram edit; returns its label. |
|
|
112
112
|
| `redo_diagram` | Redo the last undone diagram edit. |
|
|
113
113
|
|
|
114
|
+
### Save and open
|
|
115
|
+
|
|
116
|
+
The host may add the shared document tools (see `pixerate-agent-tools`). A diagram document is
|
|
117
|
+
`{ source }`: the Mermaid text with its `%% @pixerate` layout comment.
|
|
118
|
+
|
|
119
|
+
| Tool | Use |
|
|
120
|
+
| --- | --- |
|
|
121
|
+
| `get_diagram_document` | Take a copy of the whole diagram as `{ kind, formatVersion, revision, document }` before a risky rewrite, or to hand it to another session. To read the diagram, use `get_diagram`. |
|
|
122
|
+
| `load_diagram_document` | Put back a copy from `get_diagram_document` (its `document`, or the whole result). Checked first; one undo step (`undo_diagram` brings the previous diagram back). For new Mermaid text use `load_diagram_mermaid`. |
|
|
123
|
+
| `save_diagram_document` | Save the diagram under a `name` in the host's storage (same name replaces that copy). Returns the `key`. Only listed when the host has storage. Save when the user asks, not after every edit. |
|
|
124
|
+
| `list_saved_diagram_documents` | List saved diagrams (`key`, `name`, `savedAt`), newest first. |
|
|
125
|
+
| `open_saved_diagram_document` | Replace the diagram with a saved one by `key` from the list. One undo step. Ask before opening over unsaved work the user has not saved. |
|
|
126
|
+
|
|
114
127
|
## Pitfalls
|
|
115
128
|
|
|
116
129
|
- Ids are Mermaid ids, not labels. Always take them from `get_diagram` or from what a write
|