@pixerate/editor 0.277.0 → 0.279.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/dist/canvas/editorDocument.d.cts +53 -0
- package/dist/canvas/editorDocument.d.ts +53 -0
- package/dist/canvas/index.cjs +169 -22
- package/dist/canvas/index.js +148 -1
- package/dist/chart/index.cjs +1 -1
- package/dist/chart/index.js +1 -1
- package/dist/{chunk-T2HUQBRC.cjs → chunk-DK33HLA2.cjs} +331 -10
- package/dist/{chunk-JV7DKLMX.js → chunk-KBH6CM3U.js} +326 -5
- package/dist/paint-editor/controller.d.cts +40 -8
- package/dist/paint-editor/controller.d.ts +40 -8
- package/dist/paint-editor/index.cjs +473 -254
- package/dist/paint-editor/index.js +265 -46
- package/dist/paint-editor/serialize.d.cts +3 -1
- package/dist/paint-editor/serialize.d.ts +3 -1
- package/dist/paint-editor/stroke.d.cts +3 -0
- package/dist/paint-editor/stroke.d.ts +3 -0
- package/dist/paint-editor/strokes.d.cts +11 -1
- package/dist/paint-editor/strokes.d.ts +11 -1
- package/dist/persistence/autosave.d.cts +15 -1
- package/dist/persistence/autosave.d.ts +15 -1
- package/dist/persistence/editorAutosave.d.cts +12 -1
- package/dist/persistence/editorAutosave.d.ts +12 -1
- package/dist/persistence/index.cjs +6 -2
- package/dist/persistence/index.d.cts +1 -0
- package/dist/persistence/index.d.ts +1 -0
- package/dist/persistence/index.js +5 -1
- package/dist/persistence/remoteSync.d.cts +152 -0
- package/dist/persistence/remoteSync.d.ts +152 -0
- package/package.json +1 -1
- package/skills/pixerate-paint-editor/SKILL.md +2 -2
|
@@ -473,6 +473,10 @@ function createAutosaveController(options) {
|
|
|
473
473
|
return rev;
|
|
474
474
|
};
|
|
475
475
|
const docName = () => typeof options.name === "function" ? options.name() : options.name;
|
|
476
|
+
const readBaseVersion = () => {
|
|
477
|
+
const v = typeof options.baseVersion === "function" ? options.baseVersion() : options.baseVersion;
|
|
478
|
+
return typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
479
|
+
};
|
|
476
480
|
const run = (action, rev) => {
|
|
477
481
|
if (action.type === "writeSnapshot") {
|
|
478
482
|
setInfo({ status: "saving", nextAttemptAt: null });
|
|
@@ -482,11 +486,13 @@ function createAutosaveController(options) {
|
|
|
482
486
|
try {
|
|
483
487
|
const text = serialize(getSnapshot());
|
|
484
488
|
await storage.set(key, text);
|
|
489
|
+
const baseVersion = readBaseVersion();
|
|
485
490
|
const meta = {
|
|
486
491
|
dirty: true,
|
|
487
492
|
revision: action.revision,
|
|
488
493
|
time: wallClock(),
|
|
489
|
-
name: docName()
|
|
494
|
+
name: docName(),
|
|
495
|
+
...baseVersion !== void 0 ? { baseVersion } : {}
|
|
490
496
|
};
|
|
491
497
|
await storage.set(metaKey, JSON.stringify(meta));
|
|
492
498
|
ok = true;
|
|
@@ -607,7 +613,7 @@ function createAutosaveController(options) {
|
|
|
607
613
|
tick();
|
|
608
614
|
await whenIdle();
|
|
609
615
|
},
|
|
610
|
-
async checkRecovery() {
|
|
616
|
+
async checkRecovery(checkOptions) {
|
|
611
617
|
if (policy.ownsSnapshot) return null;
|
|
612
618
|
let meta;
|
|
613
619
|
try {
|
|
@@ -618,6 +624,12 @@ function createAutosaveController(options) {
|
|
|
618
624
|
return null;
|
|
619
625
|
}
|
|
620
626
|
if (!meta || meta.dirty !== true) return null;
|
|
627
|
+
const required = checkOptions?.minimumBaseVersion ?? readBaseVersion();
|
|
628
|
+
if (required !== void 0 && (typeof meta.baseVersion === "number" ? meta.baseVersion : 0) < required) {
|
|
629
|
+
await storage.remove(metaKey).catch(() => void 0);
|
|
630
|
+
await storage.remove(key).catch(() => void 0);
|
|
631
|
+
return null;
|
|
632
|
+
}
|
|
621
633
|
const text = await storage.get(key).catch(() => null);
|
|
622
634
|
if (text === null) return null;
|
|
623
635
|
try {
|
|
@@ -726,10 +738,36 @@ function createEditorAutosave(doc, options) {
|
|
|
726
738
|
savedRevision: revisions.savedRevision
|
|
727
739
|
});
|
|
728
740
|
const unsubscribe = revisions.subscribe(() => controller.notify());
|
|
741
|
+
const retireRecovery = async () => {
|
|
742
|
+
await controller.discardRecovery();
|
|
743
|
+
controller.ready();
|
|
744
|
+
await collect();
|
|
745
|
+
};
|
|
729
746
|
return {
|
|
730
747
|
controller,
|
|
731
748
|
revisions,
|
|
732
749
|
checkRecovery: () => controller.checkRecovery(),
|
|
750
|
+
async recoverIfNewer(minimumBaseVersion) {
|
|
751
|
+
const recovery = await controller.checkRecovery(
|
|
752
|
+
minimumBaseVersion !== void 0 ? { minimumBaseVersion } : void 0
|
|
753
|
+
);
|
|
754
|
+
if (!recovery) {
|
|
755
|
+
controller.ready();
|
|
756
|
+
return null;
|
|
757
|
+
}
|
|
758
|
+
if (!recovery.snapshot) {
|
|
759
|
+
await retireRecovery();
|
|
760
|
+
return null;
|
|
761
|
+
}
|
|
762
|
+
try {
|
|
763
|
+
const report = doc.loadDocument(recovery.snapshot, { history: "reset" });
|
|
764
|
+
controller.ready();
|
|
765
|
+
return report;
|
|
766
|
+
} catch {
|
|
767
|
+
await retireRecovery();
|
|
768
|
+
return null;
|
|
769
|
+
}
|
|
770
|
+
},
|
|
733
771
|
restore(recovery) {
|
|
734
772
|
if (!recovery.snapshot) throw recovery.error ?? new Error("The recovered snapshot could not be read.");
|
|
735
773
|
const report = doc.loadDocument(recovery.snapshot, { history: "reset" });
|
|
@@ -737,9 +775,7 @@ function createEditorAutosave(doc, options) {
|
|
|
737
775
|
return report;
|
|
738
776
|
},
|
|
739
777
|
async discard() {
|
|
740
|
-
await
|
|
741
|
-
controller.ready();
|
|
742
|
-
await collect();
|
|
778
|
+
await retireRecovery();
|
|
743
779
|
},
|
|
744
780
|
async markClean() {
|
|
745
781
|
revisions.markSaved();
|
|
@@ -754,6 +790,289 @@ function createEditorAutosave(doc, options) {
|
|
|
754
790
|
};
|
|
755
791
|
}
|
|
756
792
|
|
|
793
|
+
// src/persistence/remoteSync.ts
|
|
794
|
+
var EditorRemoteConflictError = class extends Error {
|
|
795
|
+
constructor(serverVersion) {
|
|
796
|
+
super(
|
|
797
|
+
serverVersion === void 0 ? "The document changed on the server since it was loaded; local edits were not saved." : `The document is at version ${serverVersion} on the server; local edits were not saved.`
|
|
798
|
+
);
|
|
799
|
+
this.serverVersion = serverVersion;
|
|
800
|
+
this.name = "EditorRemoteConflictError";
|
|
801
|
+
}
|
|
802
|
+
serverVersion;
|
|
803
|
+
};
|
|
804
|
+
var cloneSnapshot = (value) => {
|
|
805
|
+
try {
|
|
806
|
+
return JSON.parse(JSON.stringify(value));
|
|
807
|
+
} catch {
|
|
808
|
+
return value;
|
|
809
|
+
}
|
|
810
|
+
};
|
|
811
|
+
function createEditorRemoteSync(doc, options) {
|
|
812
|
+
const debounceMs = options.debounceMs ?? 500;
|
|
813
|
+
const now = options.now ?? Date.now;
|
|
814
|
+
let localVersion = options.initialVersion ?? 0;
|
|
815
|
+
let confirmedVersion = localVersion;
|
|
816
|
+
let localSequence = 0;
|
|
817
|
+
let savedSequence = 0;
|
|
818
|
+
let baselineSnapshot = cloneSnapshot(doc.getDocument());
|
|
819
|
+
let savedSnapshot = baselineSnapshot;
|
|
820
|
+
let state = "idle";
|
|
821
|
+
let lastSavedAt = null;
|
|
822
|
+
let lastError = void 0;
|
|
823
|
+
let isApplyingRemote = false;
|
|
824
|
+
let conflictRetries = 0;
|
|
825
|
+
let destroyed = false;
|
|
826
|
+
let debounceTimer = null;
|
|
827
|
+
let inFlightPromise = null;
|
|
828
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
829
|
+
const getStatus = () => ({
|
|
830
|
+
status: state,
|
|
831
|
+
version: localVersion,
|
|
832
|
+
savedVersion: confirmedVersion,
|
|
833
|
+
dirty: localSequence > savedSequence,
|
|
834
|
+
lastSavedAt,
|
|
835
|
+
...lastError !== void 0 ? { error: lastError } : {}
|
|
836
|
+
});
|
|
837
|
+
const emit = () => {
|
|
838
|
+
const currentStatus = getStatus();
|
|
839
|
+
listeners.forEach((listener) => {
|
|
840
|
+
try {
|
|
841
|
+
listener(currentStatus);
|
|
842
|
+
} catch (error) {
|
|
843
|
+
console.error("[remoteSync] Error in status listener:", error);
|
|
844
|
+
}
|
|
845
|
+
});
|
|
846
|
+
};
|
|
847
|
+
const clearTimer = () => {
|
|
848
|
+
if (debounceTimer !== null) {
|
|
849
|
+
clearTimeout(debounceTimer);
|
|
850
|
+
debounceTimer = null;
|
|
851
|
+
}
|
|
852
|
+
};
|
|
853
|
+
const hasChanged = (current, baseline) => {
|
|
854
|
+
if (options.diff) {
|
|
855
|
+
const diff = options.diff(current, baseline);
|
|
856
|
+
const empty = options.isEmptyDiff ? options.isEmptyDiff(diff, current, baseline) : JSON.stringify(current) === JSON.stringify(baseline);
|
|
857
|
+
return { changed: !empty, diff };
|
|
858
|
+
}
|
|
859
|
+
return { changed: JSON.stringify(current) !== JSON.stringify(baseline) };
|
|
860
|
+
};
|
|
861
|
+
const suppressed = (fn) => {
|
|
862
|
+
const wasApplying = isApplyingRemote;
|
|
863
|
+
isApplyingRemote = true;
|
|
864
|
+
try {
|
|
865
|
+
return fn();
|
|
866
|
+
} finally {
|
|
867
|
+
isApplyingRemote = wasApplying;
|
|
868
|
+
}
|
|
869
|
+
};
|
|
870
|
+
const executeSave = async () => {
|
|
871
|
+
if (inFlightPromise) {
|
|
872
|
+
await inFlightPromise;
|
|
873
|
+
if (localSequence > savedSequence) {
|
|
874
|
+
return executeSave();
|
|
875
|
+
}
|
|
876
|
+
return true;
|
|
877
|
+
}
|
|
878
|
+
if (localSequence <= savedSequence) {
|
|
879
|
+
return true;
|
|
880
|
+
}
|
|
881
|
+
clearTimer();
|
|
882
|
+
const targetSequence = localSequence;
|
|
883
|
+
const snapshotToSave = cloneSnapshot(baselineSnapshot);
|
|
884
|
+
const diffToSave = options.diff ? options.diff(snapshotToSave, savedSnapshot) : void 0;
|
|
885
|
+
const versionToSave = Math.max(localVersion, confirmedVersion + 1);
|
|
886
|
+
localVersion = versionToSave;
|
|
887
|
+
state = "saving";
|
|
888
|
+
lastError = void 0;
|
|
889
|
+
emit();
|
|
890
|
+
const run = (async () => {
|
|
891
|
+
try {
|
|
892
|
+
const result = await options.save({
|
|
893
|
+
document: snapshotToSave,
|
|
894
|
+
version: versionToSave,
|
|
895
|
+
baseVersion: confirmedVersion,
|
|
896
|
+
diff: diffToSave
|
|
897
|
+
});
|
|
898
|
+
if (result && typeof result === "object" && result.conflict) {
|
|
899
|
+
const serverVersion = typeof result.version === "number" ? result.version : void 0;
|
|
900
|
+
if (!options.onConflict || conflictRetries >= (options.maxConflictRetries ?? 3)) {
|
|
901
|
+
conflictRetries = 0;
|
|
902
|
+
throw new EditorRemoteConflictError(serverVersion);
|
|
903
|
+
}
|
|
904
|
+
conflictRetries++;
|
|
905
|
+
const resolution = await options.onConflict({
|
|
906
|
+
document: snapshotToSave,
|
|
907
|
+
base: savedSnapshot,
|
|
908
|
+
serverVersion,
|
|
909
|
+
apply: (fn) => suppressed(fn)
|
|
910
|
+
});
|
|
911
|
+
confirmedVersion = resolution.serverVersion;
|
|
912
|
+
localVersion = Math.max(localVersion, confirmedVersion);
|
|
913
|
+
if (resolution.serverDocument !== void 0) savedSnapshot = cloneSnapshot(resolution.serverDocument);
|
|
914
|
+
baselineSnapshot = cloneSnapshot(doc.getDocument());
|
|
915
|
+
localSequence++;
|
|
916
|
+
state = "pending";
|
|
917
|
+
emit();
|
|
918
|
+
return true;
|
|
919
|
+
}
|
|
920
|
+
conflictRetries = 0;
|
|
921
|
+
const returnedVersion = typeof result === "number" ? result : result && typeof result === "object" && typeof result.version === "number" ? result.version : versionToSave;
|
|
922
|
+
confirmedVersion = Math.max(confirmedVersion, versionToSave, returnedVersion);
|
|
923
|
+
if (returnedVersion > localVersion) {
|
|
924
|
+
localVersion = returnedVersion;
|
|
925
|
+
}
|
|
926
|
+
savedSequence = Math.max(savedSequence, targetSequence);
|
|
927
|
+
savedSnapshot = snapshotToSave;
|
|
928
|
+
lastSavedAt = now();
|
|
929
|
+
lastError = void 0;
|
|
930
|
+
if (localSequence <= savedSequence) {
|
|
931
|
+
state = "saved";
|
|
932
|
+
try {
|
|
933
|
+
await options.autosave?.markClean();
|
|
934
|
+
} catch (error) {
|
|
935
|
+
console.error("[remoteSync] autosave.markClean failed:", error);
|
|
936
|
+
}
|
|
937
|
+
} else {
|
|
938
|
+
state = "pending";
|
|
939
|
+
}
|
|
940
|
+
options.onSaved?.({ document: snapshotToSave, version: confirmedVersion });
|
|
941
|
+
emit();
|
|
942
|
+
return true;
|
|
943
|
+
} catch (error) {
|
|
944
|
+
lastError = error;
|
|
945
|
+
state = "error";
|
|
946
|
+
options.onError?.(error);
|
|
947
|
+
emit();
|
|
948
|
+
return false;
|
|
949
|
+
}
|
|
950
|
+
})();
|
|
951
|
+
inFlightPromise = run;
|
|
952
|
+
let ok;
|
|
953
|
+
try {
|
|
954
|
+
ok = await run;
|
|
955
|
+
} finally {
|
|
956
|
+
if (inFlightPromise === run) inFlightPromise = null;
|
|
957
|
+
}
|
|
958
|
+
if (ok && localSequence > savedSequence && debounceTimer === null) {
|
|
959
|
+
return executeSave();
|
|
960
|
+
}
|
|
961
|
+
return ok;
|
|
962
|
+
};
|
|
963
|
+
const scheduleSave = () => {
|
|
964
|
+
clearTimer();
|
|
965
|
+
if (debounceMs <= 0) {
|
|
966
|
+
void executeSave();
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
debounceTimer = setTimeout(() => {
|
|
970
|
+
debounceTimer = null;
|
|
971
|
+
if (!destroyed) {
|
|
972
|
+
void executeSave();
|
|
973
|
+
}
|
|
974
|
+
}, debounceMs);
|
|
975
|
+
};
|
|
976
|
+
const notify = () => {
|
|
977
|
+
if (isApplyingRemote || destroyed) return false;
|
|
978
|
+
const current = cloneSnapshot(doc.getDocument());
|
|
979
|
+
const { changed, diff } = hasChanged(current, baselineSnapshot);
|
|
980
|
+
if (!changed) return false;
|
|
981
|
+
const previousBaseline = baselineSnapshot;
|
|
982
|
+
baselineSnapshot = current;
|
|
983
|
+
localSequence++;
|
|
984
|
+
localVersion = Math.max(localVersion, confirmedVersion) + 1;
|
|
985
|
+
if (state !== "saving") {
|
|
986
|
+
state = "pending";
|
|
987
|
+
}
|
|
988
|
+
emit();
|
|
989
|
+
options.onLocalChange?.({
|
|
990
|
+
document: current,
|
|
991
|
+
baseline: previousBaseline,
|
|
992
|
+
diff,
|
|
993
|
+
version: localVersion
|
|
994
|
+
});
|
|
995
|
+
scheduleSave();
|
|
996
|
+
return true;
|
|
997
|
+
};
|
|
998
|
+
const flush = async () => {
|
|
999
|
+
clearTimer();
|
|
1000
|
+
notify();
|
|
1001
|
+
if (localSequence <= savedSequence && !inFlightPromise) {
|
|
1002
|
+
return true;
|
|
1003
|
+
}
|
|
1004
|
+
return executeSave();
|
|
1005
|
+
};
|
|
1006
|
+
const unsubscribeDoc = doc.subscribe(() => {
|
|
1007
|
+
notify();
|
|
1008
|
+
});
|
|
1009
|
+
return {
|
|
1010
|
+
version: () => localVersion,
|
|
1011
|
+
savedVersion: () => confirmedVersion,
|
|
1012
|
+
dirty: () => localSequence > savedSequence,
|
|
1013
|
+
status: getStatus,
|
|
1014
|
+
baseline: () => baselineSnapshot,
|
|
1015
|
+
notify,
|
|
1016
|
+
applyRemote(fn, remoteVersion) {
|
|
1017
|
+
const wasApplying = isApplyingRemote;
|
|
1018
|
+
const wasDirty = localSequence > savedSequence;
|
|
1019
|
+
isApplyingRemote = true;
|
|
1020
|
+
try {
|
|
1021
|
+
const result = fn();
|
|
1022
|
+
baselineSnapshot = cloneSnapshot(doc.getDocument());
|
|
1023
|
+
if (typeof remoteVersion === "number" && Number.isFinite(remoteVersion)) {
|
|
1024
|
+
confirmedVersion = Math.max(confirmedVersion, remoteVersion);
|
|
1025
|
+
localVersion = Math.max(localVersion, remoteVersion);
|
|
1026
|
+
}
|
|
1027
|
+
if (!wasDirty) {
|
|
1028
|
+
savedSequence = localSequence;
|
|
1029
|
+
savedSnapshot = baselineSnapshot;
|
|
1030
|
+
}
|
|
1031
|
+
emit();
|
|
1032
|
+
return result;
|
|
1033
|
+
} finally {
|
|
1034
|
+
isApplyingRemote = wasApplying;
|
|
1035
|
+
}
|
|
1036
|
+
},
|
|
1037
|
+
resetBaseline(remoteVersion) {
|
|
1038
|
+
clearTimer();
|
|
1039
|
+
baselineSnapshot = cloneSnapshot(doc.getDocument());
|
|
1040
|
+
savedSnapshot = baselineSnapshot;
|
|
1041
|
+
if (typeof remoteVersion === "number" && Number.isFinite(remoteVersion)) {
|
|
1042
|
+
localVersion = remoteVersion;
|
|
1043
|
+
confirmedVersion = remoteVersion;
|
|
1044
|
+
}
|
|
1045
|
+
savedSequence = localSequence;
|
|
1046
|
+
lastError = void 0;
|
|
1047
|
+
state = "idle";
|
|
1048
|
+
emit();
|
|
1049
|
+
},
|
|
1050
|
+
flush,
|
|
1051
|
+
async whenIdle() {
|
|
1052
|
+
while (debounceTimer !== null || inFlightPromise) {
|
|
1053
|
+
if (debounceTimer !== null) {
|
|
1054
|
+
await flush();
|
|
1055
|
+
} else if (inFlightPromise) {
|
|
1056
|
+
await inFlightPromise.catch(() => false);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
},
|
|
1060
|
+
subscribe(listener) {
|
|
1061
|
+
listeners.add(listener);
|
|
1062
|
+
listener(getStatus());
|
|
1063
|
+
return () => {
|
|
1064
|
+
listeners.delete(listener);
|
|
1065
|
+
};
|
|
1066
|
+
},
|
|
1067
|
+
destroy() {
|
|
1068
|
+
destroyed = true;
|
|
1069
|
+
clearTimer();
|
|
1070
|
+
unsubscribeDoc();
|
|
1071
|
+
listeners.clear();
|
|
1072
|
+
}
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
|
|
757
1076
|
// src/persistence/documentStore.ts
|
|
758
1077
|
function slugifyDocumentName(name) {
|
|
759
1078
|
const slug = name.normalize("NFKD").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
@@ -1067,6 +1386,8 @@ export {
|
|
|
1067
1386
|
createAutosaveController,
|
|
1068
1387
|
createRevisionTracker,
|
|
1069
1388
|
createEditorAutosave,
|
|
1389
|
+
EditorRemoteConflictError,
|
|
1390
|
+
createEditorRemoteSync,
|
|
1070
1391
|
slugifyDocumentName,
|
|
1071
1392
|
createEditorDocumentStore,
|
|
1072
1393
|
createDocumentToolProvider
|
|
@@ -23,7 +23,7 @@ import { type PaintBrushInput, type PaintBrush } from './brushes.cjs';
|
|
|
23
23
|
import { type BrushLibrary, type BrushLibraryFile } from '../brushes/library.cjs';
|
|
24
24
|
import { type BrushPatch, type UnsettableBrushFacet } from '../brushes/patch.cjs';
|
|
25
25
|
import type { Brush } from '../brushes/types.cjs';
|
|
26
|
-
import type
|
|
26
|
+
import { type ColorScheme, type ColorValue } from '../color/dual.cjs';
|
|
27
27
|
import { type PaintCanvas, type PaintDocumentState, type PaintLayer } from './document.cjs';
|
|
28
28
|
import { type ImageEncoder, type ImageFormat } from './encode.cjs';
|
|
29
29
|
import type { PenPoint } from './engine.cjs';
|
|
@@ -206,8 +206,8 @@ export interface LiveStrokeUpdate {
|
|
|
206
206
|
brushId?: string;
|
|
207
207
|
/** Brush settings to change (as `setBrush` takes), on top of `brushId` or the stroke's brush. */
|
|
208
208
|
brush?: Omit<PaintBrushInput, 'id'>;
|
|
209
|
-
/** A new colour #rrggbb (replaces gradient or effect paint). */
|
|
210
|
-
color?:
|
|
209
|
+
/** A new colour #rrggbb, or a light / dark pair (replaces gradient or effect paint). */
|
|
210
|
+
color?: ColorValue;
|
|
211
211
|
/** Paint: a colour, gradient paint or effect paint; `null` back to the plain colour. */
|
|
212
212
|
paint?: unknown;
|
|
213
213
|
/** Another jitter seed (particles, scatter and texture fall differently). */
|
|
@@ -275,8 +275,12 @@ export interface PaintPreviewPlacement {
|
|
|
275
275
|
export interface PaintStrokeInit {
|
|
276
276
|
/** Default the active layer. */
|
|
277
277
|
layerId?: string;
|
|
278
|
-
/**
|
|
279
|
-
|
|
278
|
+
/**
|
|
279
|
+
* Default the editor colour. A light / dark pair (`DualColor`) is recorded with the stroke
|
|
280
|
+
* (`RecordedStroke.dualColor`): on a stroke layer the document draws its light side and the view
|
|
281
|
+
* the viewer's (`setColorScheme`); on a pixel layer it paints the scheme's side.
|
|
282
|
+
*/
|
|
283
|
+
color?: ColorValue;
|
|
280
284
|
/**
|
|
281
285
|
* What this stroke paints with, instead of the editor's paint: a colour, gradient paint
|
|
282
286
|
* (`@pixerate/editor/paint` `PaintSource`), or effect paint (render it first with
|
|
@@ -1062,9 +1066,29 @@ export declare class PaintEditorController {
|
|
|
1062
1066
|
private brushColour;
|
|
1063
1067
|
/**
|
|
1064
1068
|
* The host's colour scheme: a paint colour that came from a brush's light / dark default
|
|
1065
|
-
* resolves to the new side
|
|
1069
|
+
* resolves to the new side, and stroke layers show their dual-colour strokes in it. Only the
|
|
1070
|
+
* view changes: the document, history and change listeners don't hear of it.
|
|
1066
1071
|
*/
|
|
1067
1072
|
setColorScheme(scheme: ColorScheme): void;
|
|
1073
|
+
/**
|
|
1074
|
+
* Stroke layers in the colour scheme: the document keeps every stroke layer drawn with each
|
|
1075
|
+
* stroke's `color` (a dual-colour stroke's light side), so saved and shared documents never
|
|
1076
|
+
* depend on who is looking. The view (display, image exports, the base a pointer stroke paints
|
|
1077
|
+
* on) draws dual-colour strokes in the scheme's side instead. One cached surface per layer,
|
|
1078
|
+
* brought up to date from the strokes that changed since it was drawn.
|
|
1079
|
+
*/
|
|
1080
|
+
private schemeViews;
|
|
1081
|
+
private schemeCopies;
|
|
1082
|
+
/** Set while a stroke is painted for the document rather than the view (`runStroke` on a stroke layer): dual colours take this side. */
|
|
1083
|
+
private paintScheme;
|
|
1084
|
+
/** A stroke as the scheme shows it: itself, or (a dual-colour stroke) a copy in the scheme's side. */
|
|
1085
|
+
private schemeStroke;
|
|
1086
|
+
/** A stroke layer's surface in the scheme, or `null` when the document's pixels already show it. */
|
|
1087
|
+
private schemeSurface;
|
|
1088
|
+
/** `layers` with each stroke layer that holds dual-colour strokes shown in the scheme. */
|
|
1089
|
+
private schemeLayers;
|
|
1090
|
+
/** The paint colour's light / dark pair for a stroke with `brushId`, when the colour came from one (a brush's own or default colour, or `setColor`). */
|
|
1091
|
+
private dualPaint;
|
|
1068
1092
|
/** Locks the paint colour: choosing a brush with a default colour keeps it. Editor state, not an undo step. */
|
|
1069
1093
|
setColorLocked(locked: boolean): void;
|
|
1070
1094
|
/**
|
|
@@ -1075,8 +1099,12 @@ export declare class PaintEditorController {
|
|
|
1075
1099
|
private takeBrushColour;
|
|
1076
1100
|
/** A brush that always paints its own colour (`colour.own`): that colour, resolved for the scheme now; else `null`. */
|
|
1077
1101
|
private ownColour;
|
|
1078
|
-
/**
|
|
1079
|
-
|
|
1102
|
+
/**
|
|
1103
|
+
* Sets the paint colour (and goes back to one colour from gradient paint). A light / dark pair
|
|
1104
|
+
* paints the colour scheme's side, follows `setColorScheme`, and is recorded with each stroke
|
|
1105
|
+
* painted in it. Returns the colour as #rrggbb.
|
|
1106
|
+
*/
|
|
1107
|
+
setColor(color: ColorValue): string;
|
|
1080
1108
|
/**
|
|
1081
1109
|
* Sets what strokes paint with: one colour (`{ type: 'color', color }` or `#rrggbb`), or gradient
|
|
1082
1110
|
* paint (`{ type: 'gradient', stops, space?, map?, softness? }`, see `@pixerate/editor/paint`),
|
|
@@ -1775,6 +1803,10 @@ export declare class PaintEditorController {
|
|
|
1775
1803
|
liveStrokeArea(id: string): RasterRect | null;
|
|
1776
1804
|
/** Commits a layer's new stroke list with its redrawn pixels, recorded for replays (one undo step). */
|
|
1777
1805
|
private commitLiveStrokes;
|
|
1806
|
+
/**
|
|
1807
|
+
* The document with a layer's strokes set and its pixels redrawn. With `record` false (the same
|
|
1808
|
+
* strokes drawn for the colour scheme) the pixel lock doesn't apply and no event is recorded for replays.
|
|
1809
|
+
*/
|
|
1778
1810
|
private withLiveStrokes;
|
|
1779
1811
|
/** Where a stroke painted (document pixels): as learnt when it was painted or drawn, else an estimate from its points and brush. */
|
|
1780
1812
|
private strokeArea;
|
|
@@ -23,7 +23,7 @@ import { type PaintBrushInput, type PaintBrush } from './brushes.js';
|
|
|
23
23
|
import { type BrushLibrary, type BrushLibraryFile } from '../brushes/library.js';
|
|
24
24
|
import { type BrushPatch, type UnsettableBrushFacet } from '../brushes/patch.js';
|
|
25
25
|
import type { Brush } from '../brushes/types.js';
|
|
26
|
-
import type
|
|
26
|
+
import { type ColorScheme, type ColorValue } from '../color/dual.js';
|
|
27
27
|
import { type PaintCanvas, type PaintDocumentState, type PaintLayer } from './document.js';
|
|
28
28
|
import { type ImageEncoder, type ImageFormat } from './encode.js';
|
|
29
29
|
import type { PenPoint } from './engine.js';
|
|
@@ -206,8 +206,8 @@ export interface LiveStrokeUpdate {
|
|
|
206
206
|
brushId?: string;
|
|
207
207
|
/** Brush settings to change (as `setBrush` takes), on top of `brushId` or the stroke's brush. */
|
|
208
208
|
brush?: Omit<PaintBrushInput, 'id'>;
|
|
209
|
-
/** A new colour #rrggbb (replaces gradient or effect paint). */
|
|
210
|
-
color?:
|
|
209
|
+
/** A new colour #rrggbb, or a light / dark pair (replaces gradient or effect paint). */
|
|
210
|
+
color?: ColorValue;
|
|
211
211
|
/** Paint: a colour, gradient paint or effect paint; `null` back to the plain colour. */
|
|
212
212
|
paint?: unknown;
|
|
213
213
|
/** Another jitter seed (particles, scatter and texture fall differently). */
|
|
@@ -275,8 +275,12 @@ export interface PaintPreviewPlacement {
|
|
|
275
275
|
export interface PaintStrokeInit {
|
|
276
276
|
/** Default the active layer. */
|
|
277
277
|
layerId?: string;
|
|
278
|
-
/**
|
|
279
|
-
|
|
278
|
+
/**
|
|
279
|
+
* Default the editor colour. A light / dark pair (`DualColor`) is recorded with the stroke
|
|
280
|
+
* (`RecordedStroke.dualColor`): on a stroke layer the document draws its light side and the view
|
|
281
|
+
* the viewer's (`setColorScheme`); on a pixel layer it paints the scheme's side.
|
|
282
|
+
*/
|
|
283
|
+
color?: ColorValue;
|
|
280
284
|
/**
|
|
281
285
|
* What this stroke paints with, instead of the editor's paint: a colour, gradient paint
|
|
282
286
|
* (`@pixerate/editor/paint` `PaintSource`), or effect paint (render it first with
|
|
@@ -1062,9 +1066,29 @@ export declare class PaintEditorController {
|
|
|
1062
1066
|
private brushColour;
|
|
1063
1067
|
/**
|
|
1064
1068
|
* The host's colour scheme: a paint colour that came from a brush's light / dark default
|
|
1065
|
-
* resolves to the new side
|
|
1069
|
+
* resolves to the new side, and stroke layers show their dual-colour strokes in it. Only the
|
|
1070
|
+
* view changes: the document, history and change listeners don't hear of it.
|
|
1066
1071
|
*/
|
|
1067
1072
|
setColorScheme(scheme: ColorScheme): void;
|
|
1073
|
+
/**
|
|
1074
|
+
* Stroke layers in the colour scheme: the document keeps every stroke layer drawn with each
|
|
1075
|
+
* stroke's `color` (a dual-colour stroke's light side), so saved and shared documents never
|
|
1076
|
+
* depend on who is looking. The view (display, image exports, the base a pointer stroke paints
|
|
1077
|
+
* on) draws dual-colour strokes in the scheme's side instead. One cached surface per layer,
|
|
1078
|
+
* brought up to date from the strokes that changed since it was drawn.
|
|
1079
|
+
*/
|
|
1080
|
+
private schemeViews;
|
|
1081
|
+
private schemeCopies;
|
|
1082
|
+
/** Set while a stroke is painted for the document rather than the view (`runStroke` on a stroke layer): dual colours take this side. */
|
|
1083
|
+
private paintScheme;
|
|
1084
|
+
/** A stroke as the scheme shows it: itself, or (a dual-colour stroke) a copy in the scheme's side. */
|
|
1085
|
+
private schemeStroke;
|
|
1086
|
+
/** A stroke layer's surface in the scheme, or `null` when the document's pixels already show it. */
|
|
1087
|
+
private schemeSurface;
|
|
1088
|
+
/** `layers` with each stroke layer that holds dual-colour strokes shown in the scheme. */
|
|
1089
|
+
private schemeLayers;
|
|
1090
|
+
/** The paint colour's light / dark pair for a stroke with `brushId`, when the colour came from one (a brush's own or default colour, or `setColor`). */
|
|
1091
|
+
private dualPaint;
|
|
1068
1092
|
/** Locks the paint colour: choosing a brush with a default colour keeps it. Editor state, not an undo step. */
|
|
1069
1093
|
setColorLocked(locked: boolean): void;
|
|
1070
1094
|
/**
|
|
@@ -1075,8 +1099,12 @@ export declare class PaintEditorController {
|
|
|
1075
1099
|
private takeBrushColour;
|
|
1076
1100
|
/** A brush that always paints its own colour (`colour.own`): that colour, resolved for the scheme now; else `null`. */
|
|
1077
1101
|
private ownColour;
|
|
1078
|
-
/**
|
|
1079
|
-
|
|
1102
|
+
/**
|
|
1103
|
+
* Sets the paint colour (and goes back to one colour from gradient paint). A light / dark pair
|
|
1104
|
+
* paints the colour scheme's side, follows `setColorScheme`, and is recorded with each stroke
|
|
1105
|
+
* painted in it. Returns the colour as #rrggbb.
|
|
1106
|
+
*/
|
|
1107
|
+
setColor(color: ColorValue): string;
|
|
1080
1108
|
/**
|
|
1081
1109
|
* Sets what strokes paint with: one colour (`{ type: 'color', color }` or `#rrggbb`), or gradient
|
|
1082
1110
|
* paint (`{ type: 'gradient', stops, space?, map?, softness? }`, see `@pixerate/editor/paint`),
|
|
@@ -1775,6 +1803,10 @@ export declare class PaintEditorController {
|
|
|
1775
1803
|
liveStrokeArea(id: string): RasterRect | null;
|
|
1776
1804
|
/** Commits a layer's new stroke list with its redrawn pixels, recorded for replays (one undo step). */
|
|
1777
1805
|
private commitLiveStrokes;
|
|
1806
|
+
/**
|
|
1807
|
+
* The document with a layer's strokes set and its pixels redrawn. With `record` false (the same
|
|
1808
|
+
* strokes drawn for the colour scheme) the pixel lock doesn't apply and no event is recorded for replays.
|
|
1809
|
+
*/
|
|
1778
1810
|
private withLiveStrokes;
|
|
1779
1811
|
/** Where a stroke painted (document pixels): as learnt when it was painted or drawn, else an estimate from its points and brush. */
|
|
1780
1812
|
private strokeArea;
|