@lvl99dotcom/paintcanvas 0.6.0 → 0.8.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.
Files changed (119) hide show
  1. package/README.md +3 -2
  2. package/dist/lib/assets/index.d.ts +92 -0
  3. package/dist/lib/assets/index.js +106 -0
  4. package/dist/lib/assets/schema.d.ts +151 -0
  5. package/dist/lib/assets/schema.js +76 -0
  6. package/dist/lib/brushes/presetFile/index.d.ts +6 -27
  7. package/dist/lib/brushes/presetFile/index.js +34 -47
  8. package/dist/lib/brushes/types/index.d.ts +2 -11
  9. package/dist/lib/classes/AssetSync/index.d.ts +45 -0
  10. package/dist/lib/classes/AssetSync/index.js +80 -0
  11. package/dist/lib/classes/ColourPalettes/index.d.ts +83 -0
  12. package/dist/lib/classes/ColourPalettes/index.js +165 -0
  13. package/dist/lib/classes/CustomPresets/index.d.ts +14 -15
  14. package/dist/lib/classes/CustomPresets/index.js +25 -10
  15. package/dist/lib/classes/LayerAutosave/index.d.ts +9 -2
  16. package/dist/lib/classes/LayerAutosave/index.js +16 -6
  17. package/dist/lib/classes/PaintCanvas/index.d.ts +124 -28
  18. package/dist/lib/classes/PaintCanvas/index.js +228 -63
  19. package/dist/lib/classes/Palette/index.d.ts +3 -2
  20. package/dist/lib/classes/Palette/index.js +3 -2
  21. package/dist/lib/classes/ProfileGraphs/index.d.ts +13 -2
  22. package/dist/lib/classes/ProfileGraphs/index.js +22 -2
  23. package/dist/lib/classes/RemoteDocumentAutosave/index.d.ts +73 -0
  24. package/dist/lib/classes/RemoteDocumentAutosave/index.js +119 -0
  25. package/dist/lib/classes/SettingsAutosave/index.d.ts +7 -3
  26. package/dist/lib/classes/SettingsAutosave/index.js +28 -7
  27. package/dist/lib/classes/SettingsExtensions/index.d.ts +67 -0
  28. package/dist/lib/classes/SettingsExtensions/index.js +88 -0
  29. package/dist/lib/classes/UiSettings/index.d.ts +24 -2
  30. package/dist/lib/classes/UiSettings/index.js +32 -7
  31. package/dist/lib/document/index.d.ts +61 -4
  32. package/dist/lib/document/index.js +173 -4
  33. package/dist/lib/document/schema.d.ts +154 -39
  34. package/dist/lib/document/schema.js +91 -25
  35. package/dist/lib/messages/schema.d.ts +176 -74
  36. package/dist/lib/messages/schema.js +2 -1
  37. package/dist/lib/palette/index.d.ts +8 -42
  38. package/dist/lib/palette/index.js +26 -59
  39. package/dist/lib/palette/schema.d.ts +111 -22
  40. package/dist/lib/palette/schema.js +51 -26
  41. package/dist/lib/presets/index.d.ts +1 -9
  42. package/dist/lib/presets/index.js +4 -10
  43. package/dist/lib/presets/schema.d.ts +118 -73
  44. package/dist/lib/presets/schema.js +28 -60
  45. package/dist/lib/profiles/profileFile/index.d.ts +10 -0
  46. package/dist/lib/profiles/profileFile/index.js +19 -0
  47. package/dist/lib/profiles/schema.d.ts +96 -3
  48. package/dist/lib/profiles/schema.js +31 -7
  49. package/dist/lib/schemas/index.d.ts +42 -0
  50. package/dist/lib/schemas/index.js +60 -0
  51. package/dist/lib/settings/index.d.ts +37 -22
  52. package/dist/lib/settings/index.js +78 -37
  53. package/dist/lib/settings/schema.d.ts +202 -77
  54. package/dist/lib/settings/schema.js +93 -86
  55. package/dist/lib/storage/index.d.ts +108 -3
  56. package/dist/lib/storage/index.js +119 -5
  57. package/dist/lib/validation/validators.generated.d.ts +19 -8
  58. package/dist/lib/validation/validators.generated.js +44723 -18889
  59. package/dist/lib/version/index.d.ts +25 -0
  60. package/dist/lib/version/index.js +35 -0
  61. package/dist/locales/en-GB.js +28 -5
  62. package/dist/locales/en-US.js +28 -5
  63. package/dist/locales/fr.js +28 -5
  64. package/dist/locales/ja.js +28 -5
  65. package/dist/locales/ko.js +28 -5
  66. package/dist/locales/zh-Hans.js +28 -5
  67. package/dist/locales/zh-Hant.js +28 -5
  68. package/dist/package.js +1 -1
  69. package/dist/testing/app.d.ts +6 -2
  70. package/dist/testing/app.js +7 -2
  71. package/dist/testing/documentStore.d.ts +26 -0
  72. package/dist/testing/documentStore.js +31 -0
  73. package/dist/ui/elements/PaintCanvas/index.d.ts +19 -5
  74. package/dist/ui/elements/PaintCanvas/index.js +41 -10
  75. package/dist/ui/elements/PcAppSettings/index.d.ts +14 -7
  76. package/dist/ui/elements/PcAppSettings/index.js +58 -33
  77. package/dist/ui/elements/PcBrushConfig/index.d.ts +1 -0
  78. package/dist/ui/elements/PcBrushConfig/index.js +8 -9
  79. package/dist/ui/elements/PcBrushTest/index.d.ts +1 -0
  80. package/dist/ui/elements/PcBrushTest/index.js +8 -7
  81. package/dist/ui/elements/PcBrushTest/style.js +1 -1
  82. package/dist/ui/elements/PcButtonMenu/index.d.ts +88 -0
  83. package/dist/ui/elements/PcButtonMenu/index.js +125 -0
  84. package/dist/ui/elements/PcCanvas/index.d.ts +9 -0
  85. package/dist/ui/elements/PcCanvas/index.js +33 -17
  86. package/dist/ui/elements/PcClipboardTools/index.d.ts +7 -4
  87. package/dist/ui/elements/PcClipboardTools/index.js +20 -26
  88. package/dist/ui/elements/PcInputSettings/index.d.ts +2 -1
  89. package/dist/ui/elements/PcInputSettings/index.js +9 -15
  90. package/dist/ui/elements/PcLayerItem/index.d.ts +21 -6
  91. package/dist/ui/elements/PcLayerItem/index.js +53 -26
  92. package/dist/ui/elements/PcLayerItem/style.js +1 -1
  93. package/dist/ui/elements/PcPanelBrushes/index.d.ts +1 -1
  94. package/dist/ui/elements/PcPanelBrushes/index.js +22 -28
  95. package/dist/ui/elements/PcPanelBrushes/style.js +1 -1
  96. package/dist/ui/elements/PcPanelColours/index.d.ts +56 -19
  97. package/dist/ui/elements/PcPanelColours/index.js +251 -104
  98. package/dist/ui/elements/PcPanelColours/style.js +1 -1
  99. package/dist/ui/elements/PcSliderRange/index.js +3 -2
  100. package/dist/ui/elements/PcStats/index.d.ts +8 -4
  101. package/dist/ui/elements/PcStats/index.js +39 -5
  102. package/dist/ui/elements/PcToggleCheckbox/index.d.ts +13 -2
  103. package/dist/ui/elements/PcToggleCheckbox/index.js +27 -8
  104. package/dist/ui/elements/PcToolSettings/index.d.ts +1 -0
  105. package/dist/ui/elements/PcToolSettings/index.js +8 -8
  106. package/dist/ui/elements/PcVersion/index.d.ts +12 -0
  107. package/dist/ui/elements/PcVersion/index.js +43 -0
  108. package/dist/ui/index.d.ts +3 -1
  109. package/dist/ui/index.js +4 -2
  110. package/dist/ui/layout/index.d.ts +2 -2
  111. package/dist/ui/layout/index.js +2 -2
  112. package/locales/en-GB.json +28 -5
  113. package/locales/en-US.json +28 -5
  114. package/locales/fr.json +28 -5
  115. package/locales/ja.json +28 -5
  116. package/locales/ko.json +28 -5
  117. package/locales/zh-Hans.json +28 -5
  118. package/locales/zh-Hant.json +28 -5
  119. package/package.json +3 -2
@@ -1,9 +1,16 @@
1
1
  import { formatErrors } from "../presets/index.js";
2
2
  import { validateDocument } from "../validation/validators.generated.js";
3
- import { DOCUMENT_BLEND_MODES, DOCUMENT_FORMAT, DOCUMENT_VERSION, MAX_DOCUMENT_IMAGE_LENGTH, MAX_DOCUMENT_LAYERS, MAX_DOCUMENT_SHEETS, MAX_DOCUMENT_SIZE, documentSchema } from "./schema.js";
3
+ import { base64ToBytes, bytesToBase64 } from "../image/index.js";
4
+ import { compareVersions, majorVersion, parseSchemaUrl } from "../schemas/index.js";
5
+ import { AssetFileError } from "../assets/index.js";
6
+ import { DATE_TIME_PATTERN, DOCUMENT_BLEND_MODES, DOCUMENT_SCHEMA_VERSION, DOCUMENT_TYPE, MAX_DOCUMENT_AUTHORS, MAX_DOCUMENT_IMAGE_LENGTH, MAX_DOCUMENT_LAYERS, MAX_DOCUMENT_NAME_LENGTH, MAX_DOCUMENT_SHEETS, MAX_DOCUMENT_SIZE, THUMBNAIL_SIZE, documentSchema } from "./schema.js";
4
7
  // src/lib/document/index.ts
8
+ /** The `format` of documents from before versioned schemas */
9
+ var LEGACY_DOCUMENT_FORMAT = "paintcanvas-document";
10
+ /** Name of a new document, or one from before documents had names */
11
+ var UNTITLED_DOCUMENT_NAME = "Untitled";
5
12
  /**
6
- * Check a document (e.g. read from a file) against `documentSchema`, and that its selected layer exists.
13
+ * Check a document file (e.g. read from a file) against `documentSchema`, and that its selected layer exists.
7
14
  *
8
15
  * @example
9
16
  * isValidDocument(JSON.parse(text)).valid; // true
@@ -13,7 +20,7 @@ var isValidDocument = (input) => {
13
20
  valid: false,
14
21
  errors: formatErrors(validateDocument.errors)
15
22
  };
16
- if (input.activeLayer >= input.layers.length) return {
23
+ if (!input.layers.some((layer) => layer.id === input.activeLayer)) return {
17
24
  valid: false,
18
25
  errors: ["/activeLayer must be one of the layers"]
19
26
  };
@@ -22,4 +29,166 @@ var isValidDocument = (input) => {
22
29
  value: input
23
30
  };
24
31
  };
25
- export { DOCUMENT_BLEND_MODES, DOCUMENT_FORMAT, DOCUMENT_VERSION, MAX_DOCUMENT_IMAGE_LENGTH, MAX_DOCUMENT_LAYERS, MAX_DOCUMENT_SHEETS, MAX_DOCUMENT_SIZE, documentSchema, isValidDocument };
32
+ /**
33
+ * An author's details: their name alone becomes `{ name }`.
34
+ *
35
+ * @example
36
+ * authorDetails("Ada"); // { name: "Ada" }
37
+ */
38
+ var authorDetails = (author) => typeof author === "string" ? { name: author } : { ...author };
39
+ /**
40
+ * Whether two authors are the same person: by DID when both have one, otherwise by handle when both have one,
41
+ * otherwise by name.
42
+ *
43
+ * @example
44
+ * sameAuthor({ name: "Ada", handle: "ada.test" }, { name: "Ada L", handle: "ada.test" }); // true
45
+ */
46
+ var sameAuthor = (a, b) => {
47
+ if (a.did && b.did) return a.did === b.did;
48
+ if (a.handle && b.handle) return a.handle === b.handle;
49
+ return a.name === b.name;
50
+ };
51
+ /**
52
+ * A document's authors with another added at the end, unless they're already one of them.
53
+ *
54
+ * @example
55
+ * withAuthor([{ name: "Ada" }], "Ann"); // [{ name: "Ada" }, { name: "Ann" }]
56
+ */
57
+ var withAuthor = (authors, author) => {
58
+ const details = authorDetails(author);
59
+ return authors.some((other) => sameAuthor(other, details)) ? [...authors] : [...authors, details];
60
+ };
61
+ /**
62
+ * Details for a new document: a new ID, a name, an author and the time now.
63
+ *
64
+ * @example
65
+ * newDocumentInfo("Ada"); // { id: "1b9d…", name: "Untitled", author: [{ name: "Ada" }], dateCreated: "2026-…", … }
66
+ */
67
+ var newDocumentInfo = (author, name = UNTITLED_DOCUMENT_NAME, now = /* @__PURE__ */ new Date()) => ({
68
+ id: crypto.randomUUID(),
69
+ name,
70
+ author: author === void 0 ? [] : [authorDetails(author)],
71
+ dateCreated: now.toISOString(),
72
+ dateOpened: now.toISOString()
73
+ });
74
+ /**
75
+ * A PNG blob as a data URL.
76
+ */
77
+ var toDataUrl = async (image) => `data:image/png;base64,${bytesToBase64(new Uint8Array(await image.arrayBuffer()))}`;
78
+ /**
79
+ * A PNG data URL as a blob.
80
+ */
81
+ var toBlob = (dataUrl) => new Blob([base64ToBytes(dataUrl.slice(dataUrl.indexOf(",") + 1))], { type: "image/png" });
82
+ /**
83
+ * A document with each image changed by a function.
84
+ */
85
+ var mapImages = async (document, change) => {
86
+ const { layers, external, thumbnail, ...rest } = document;
87
+ return {
88
+ ...rest,
89
+ layers: await Promise.all(layers.map(async (layer) => ({
90
+ ...layer,
91
+ content: await change(layer.content)
92
+ }))),
93
+ external: await Promise.all(external.map(async (sheet) => ({
94
+ ...sheet,
95
+ content: await change(sheet.content)
96
+ }))),
97
+ ...thumbnail ? { thumbnail: {
98
+ ...thumbnail,
99
+ content: await change(thumbnail.content)
100
+ } } : {}
101
+ };
102
+ };
103
+ /**
104
+ * A document as a file (JSON), with its images as PNG data URLs.
105
+ *
106
+ * @example
107
+ * JSON.stringify(await documentToFile(await canvas.createDocument()));
108
+ */
109
+ var documentToFile = async (document) => ({
110
+ $schema: documentSchema.$id,
111
+ "@type": DOCUMENT_TYPE,
112
+ ...await mapImages(document, toDataUrl)
113
+ });
114
+ /** Whether a value is a plain object */
115
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
116
+ /**
117
+ * A document file from before versioned schemas (`format` and `version: 1`) in the current version: its layers get
118
+ * IDs, it gets new details (named `UNTITLED_DOCUMENT_NAME`, opened and made now), and the selected layer is named by ID.
119
+ */
120
+ var migrateLegacyDocument = (data, now) => {
121
+ const layers = Array.isArray(data.layers) ? data.layers : [];
122
+ const ids = layers.map(() => crypto.randomUUID());
123
+ const info = newDocumentInfo(void 0, UNTITLED_DOCUMENT_NAME, now);
124
+ return {
125
+ $schema: documentSchema.$id,
126
+ "@type": DOCUMENT_TYPE,
127
+ ...info,
128
+ width: data.width,
129
+ height: data.height,
130
+ layers: layers.map((layer, i) => ({
131
+ id: ids[i],
132
+ name: layer?.name,
133
+ visible: layer?.visible,
134
+ opacity: layer?.opacity,
135
+ blendingMode: layer?.blendMode,
136
+ alphaLock: layer?.alphaLock,
137
+ content: layer?.image
138
+ })),
139
+ activeLayer: ids[data.activeLayer] ?? "",
140
+ external: Array.isArray(data.external) ? data.external.map((sheet) => ({
141
+ source: sheet?.source,
142
+ content: sheet?.image
143
+ })) : data.external
144
+ };
145
+ };
146
+ /**
147
+ * Read a document file (e.g. parsed from JSON), with its images as PNG blobs. Files from before versioned schemas are
148
+ * migrated. Throws an `AssetFileError`: `invalid` if it isn't a document or doesn't match its schema, `unsupported` if
149
+ * it's a version the app can't read.
150
+ *
151
+ * @example
152
+ * await canvas.openDocument(documentFromFile(JSON.parse(text)));
153
+ */
154
+ var documentFromFile = (data, now = /* @__PURE__ */ new Date()) => {
155
+ if (!isRecord(data)) throw new AssetFileError("invalid");
156
+ let file = data;
157
+ const named = parseSchemaUrl(data.$schema);
158
+ if (named) {
159
+ if (named.name !== "document") throw new AssetFileError("invalid");
160
+ if (majorVersion(named.version) !== majorVersion("1.0.0") || compareVersions(named.version, "1.0.0") > 0) throw new AssetFileError("unsupported");
161
+ } else if (data.format === "paintcanvas-document" && data.version === 1) file = migrateLegacyDocument(data, now);
162
+ else throw new AssetFileError("invalid");
163
+ const result = isValidDocument(file);
164
+ if (!result.valid) throw new AssetFileError("invalid");
165
+ const document = { ...result.value };
166
+ delete document.$schema;
167
+ delete document["@type"];
168
+ const { layers, external, thumbnail, ...rest } = document;
169
+ return {
170
+ ...rest,
171
+ layers: layers.map((layer) => ({
172
+ ...layer,
173
+ content: toBlob(layer.content)
174
+ })),
175
+ external: external.map((sheet) => ({
176
+ ...sheet,
177
+ content: toBlob(sheet.content)
178
+ })),
179
+ ...thumbnail ? { thumbnail: {
180
+ ...thumbnail,
181
+ content: toBlob(thumbnail.content)
182
+ } } : {}
183
+ };
184
+ };
185
+ /**
186
+ * The name of the file a document is exported to, starting with a prefix (see `PaintCanvasNames.filePrefix`).
187
+ *
188
+ * @example
189
+ * documentFileName("Sunset at sea"); // "paintcanvas-sunset-at-sea.json"
190
+ */
191
+ var documentFileName = (name, prefix = "paintcanvas") => {
192
+ return `${prefix}-${name.toLowerCase().replace(/[^\p{L}\p{N}]+/gu, "-").replace(/^-|-$/g, "") || "document"}.json`;
193
+ };
194
+ export { DATE_TIME_PATTERN, DOCUMENT_BLEND_MODES, DOCUMENT_SCHEMA_VERSION, DOCUMENT_TYPE, LEGACY_DOCUMENT_FORMAT, MAX_DOCUMENT_AUTHORS, MAX_DOCUMENT_IMAGE_LENGTH, MAX_DOCUMENT_LAYERS, MAX_DOCUMENT_NAME_LENGTH, MAX_DOCUMENT_SHEETS, MAX_DOCUMENT_SIZE, THUMBNAIL_SIZE, UNTITLED_DOCUMENT_NAME, authorDetails, documentFileName, documentFromFile, documentSchema, documentToFile, isValidDocument, newDocumentInfo, sameAuthor, withAuthor };
@@ -1,59 +1,147 @@
1
- /** The `format` of a PaintCanvas document */
2
- export declare const DOCUMENT_FORMAT = "paintcanvas-document";
3
- /** Version of the document format */
4
- export declare const DOCUMENT_VERSION = 1;
1
+ import { type AssetAuthorDetails } from "../assets/schema.ts";
2
+ /** Version of the document schema */
3
+ export declare const DOCUMENT_SCHEMA_VERSION = "1.0.0";
4
+ /** `@type` of a document */
5
+ export declare const DOCUMENT_TYPE = "PaintCanvas/Document";
5
6
  /** Largest canvas width or height a document may have */
6
7
  export declare const MAX_DOCUMENT_SIZE = 10000;
7
8
  /** Most layers in a document (a canvas may allow fewer, see `maxLayers`) */
8
9
  export declare const MAX_DOCUMENT_LAYERS = 100;
9
10
  /** Most drawings from elsewhere in a document */
10
11
  export declare const MAX_DOCUMENT_SHEETS = 100;
11
- /** Longest image in a document (characters): a PNG data URL of up to 100 MB */
12
+ /** Most authors of a document */
13
+ export declare const MAX_DOCUMENT_AUTHORS = 100;
14
+ /** Longest document or layer name (characters) */
15
+ export declare const MAX_DOCUMENT_NAME_LENGTH = 100;
16
+ /** Longest side of a document's thumbnail (pixels) */
17
+ export declare const THUMBNAIL_SIZE = 200;
18
+ /** Longest image in a document file (characters): a PNG data URL of up to 100 MB */
12
19
  export declare const MAX_DOCUMENT_IMAGE_LENGTH: number;
13
- /** Blend modes a layer may have (see `BlendMode`) */
20
+ /** Blending modes a layer may have (see `BlendMode`) */
14
21
  export declare const DOCUMENT_BLEND_MODES: readonly ["source-over", "screen", "lighten", "multiply", "darken", "overlay"];
15
- /** A layer in a document */
16
- export interface DocumentLayer {
22
+ /** A document's details, kept with the drawing */
23
+ export interface DocumentInfo {
24
+ /** A UUID */
25
+ id: string;
26
+ name: string;
27
+ /** Who made it: whoever started it, then everyone who saved it since */
28
+ author: AssetAuthorDetails[];
29
+ /** When it was started (ISO 8601 date and time) */
30
+ dateCreated: string;
31
+ /** When it was last opened (ISO 8601 date and time) */
32
+ dateOpened: string;
33
+ }
34
+ /** A layer in a document, with its pixels as an image (`T`) the size of the canvas */
35
+ export interface DocumentLayer<T> {
36
+ id: string;
17
37
  name: string;
18
38
  visible: boolean;
19
39
  opacity: number;
20
- blendMode: (typeof DOCUMENT_BLEND_MODES)[number];
40
+ blendingMode: (typeof DOCUMENT_BLEND_MODES)[number];
21
41
  alphaLock: boolean;
22
- /** The layer's pixels as a PNG data URL, the size of the canvas */
23
- image: string;
42
+ content: T;
24
43
  }
25
- /** A drawing from elsewhere in a document (see `ExternalSheets`) */
26
- export interface DocumentSheet {
44
+ /** A drawing from elsewhere in a document (see `ExternalSheets`), with its pixels as an image (`T`) */
45
+ export interface DocumentSheet<T> {
27
46
  /** Where it came from */
28
47
  source: string;
29
- /** Its pixels as a PNG data URL, the size of the canvas */
30
- image: string;
48
+ content: T;
49
+ }
50
+ /** A small picture of a document, at most `THUMBNAIL_SIZE` pixels on its longest side */
51
+ export interface DocumentThumbnail<T> {
52
+ width: number;
53
+ height: number;
54
+ content: T;
31
55
  }
32
- /** A saved drawing */
33
- export interface PaintCanvasDocument {
34
- format: typeof DOCUMENT_FORMAT;
35
- version: typeof DOCUMENT_VERSION;
56
+ /** A document with its images as `T` */
57
+ export interface DocumentOf<T> extends DocumentInfo {
36
58
  width: number;
37
59
  height: number;
38
60
  /** The user's layers, top first */
39
- layers: DocumentLayer[];
40
- /** Index of the selected layer in `layers` */
41
- activeLayer: number;
61
+ layers: DocumentLayer<T>[];
62
+ /** ID of the selected layer */
63
+ activeLayer: string;
42
64
  /** Drawings from elsewhere, top first */
43
- external: DocumentSheet[];
65
+ external: DocumentSheet<T>[];
66
+ thumbnail?: DocumentThumbnail<T>;
67
+ }
68
+ /** A document as the app and stores use it: images are PNG blobs */
69
+ export type DrawingDocument = DocumentOf<Blob>;
70
+ /** A document as a file (JSON): images are PNG data URLs */
71
+ export interface DocumentFile extends DocumentOf<string> {
72
+ $schema: string;
73
+ "@type": typeof DOCUMENT_TYPE;
44
74
  }
45
- /** JSON schema for a document */
75
+ /** Matches an ISO 8601 date and time, e.g. `2026-11-11T09:30:00.000Z` */
76
+ export declare const DATE_TIME_PATTERN = "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2})$";
77
+ /** JSON schema for a document file */
46
78
  export declare const documentSchema: {
47
- readonly $id: "paintcanvas/document";
79
+ readonly $schema: "https://json-schema.org/draft/2020-12/schema";
80
+ readonly $id: string;
81
+ readonly "@type": "PaintCanvas/Document";
48
82
  readonly type: "object";
49
83
  readonly additionalProperties: false;
50
- readonly required: readonly ["format", "version", "width", "height", "layers", "activeLayer", "external"];
84
+ readonly required: readonly ["$schema", "@type", "id", "name", "author", "dateCreated", "dateOpened", "width", "height", "layers", "activeLayer", "external"];
51
85
  readonly properties: {
52
- readonly format: {
53
- readonly const: "paintcanvas-document";
86
+ readonly $schema: {
87
+ readonly type: "string";
88
+ readonly minLength: 1;
89
+ readonly maxLength: 2048;
54
90
  };
55
- readonly version: {
56
- readonly const: 1;
91
+ readonly "@type": {
92
+ readonly const: "PaintCanvas/Document";
93
+ };
94
+ readonly id: {
95
+ readonly type: "string";
96
+ readonly pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$";
97
+ };
98
+ readonly name: {
99
+ readonly type: "string";
100
+ readonly minLength: 1;
101
+ readonly maxLength: 100;
102
+ readonly pattern: "\\S";
103
+ };
104
+ readonly author: {
105
+ readonly type: "array";
106
+ readonly maxItems: 100;
107
+ readonly items: {
108
+ readonly type: "object";
109
+ readonly additionalProperties: false;
110
+ readonly required: readonly ["name"];
111
+ readonly properties: {
112
+ readonly did: {
113
+ readonly type: "string";
114
+ readonly maxLength: 2048;
115
+ readonly pattern: "^did:[a-z]+:[a-zA-Z0-9._:%-]+$";
116
+ };
117
+ readonly name: {
118
+ readonly type: "string";
119
+ readonly minLength: 1;
120
+ readonly maxLength: 100;
121
+ };
122
+ readonly handle: {
123
+ readonly type: "string";
124
+ readonly minLength: 1;
125
+ readonly maxLength: 100;
126
+ };
127
+ readonly email: {
128
+ readonly type: "string";
129
+ readonly maxLength: 254;
130
+ };
131
+ readonly homepage: {
132
+ readonly type: "string";
133
+ readonly maxLength: 2048;
134
+ };
135
+ };
136
+ };
137
+ };
138
+ readonly dateCreated: {
139
+ readonly type: "string";
140
+ readonly pattern: "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2})$";
141
+ };
142
+ readonly dateOpened: {
143
+ readonly type: "string";
144
+ readonly pattern: "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2})$";
57
145
  };
58
146
  readonly width: {
59
147
  readonly type: "integer";
@@ -72,12 +160,17 @@ export declare const documentSchema: {
72
160
  readonly items: {
73
161
  readonly type: "object";
74
162
  readonly additionalProperties: false;
75
- readonly required: readonly ["name", "visible", "opacity", "blendMode", "alphaLock", "image"];
163
+ readonly required: readonly ["id", "name", "visible", "opacity", "blendingMode", "alphaLock", "content"];
76
164
  readonly properties: {
77
- readonly name: {
165
+ readonly id: {
78
166
  readonly type: "string";
79
167
  readonly minLength: 1;
168
+ readonly maxLength: 100;
169
+ };
170
+ readonly name: {
80
171
  readonly maxLength: 64;
172
+ readonly type: "string";
173
+ readonly minLength: 1;
81
174
  readonly pattern: "\\S";
82
175
  };
83
176
  readonly visible: {
@@ -88,13 +181,13 @@ export declare const documentSchema: {
88
181
  readonly minimum: 0;
89
182
  readonly maximum: 1;
90
183
  };
91
- readonly blendMode: {
184
+ readonly blendingMode: {
92
185
  readonly enum: readonly ["source-over", "screen", "lighten", "multiply", "darken", "overlay"];
93
186
  };
94
187
  readonly alphaLock: {
95
188
  readonly type: "boolean";
96
189
  };
97
- readonly image: {
190
+ readonly content: {
98
191
  readonly type: "string";
99
192
  readonly pattern: "^data:image/png;base64,[A-Za-z0-9+/]*={0,2}$";
100
193
  readonly maxLength: number;
@@ -103,9 +196,9 @@ export declare const documentSchema: {
103
196
  };
104
197
  };
105
198
  readonly activeLayer: {
106
- readonly type: "integer";
107
- readonly minimum: 0;
108
- readonly maximum: number;
199
+ readonly type: "string";
200
+ readonly minLength: 1;
201
+ readonly maxLength: 100;
109
202
  };
110
203
  readonly external: {
111
204
  readonly type: "array";
@@ -113,14 +206,14 @@ export declare const documentSchema: {
113
206
  readonly items: {
114
207
  readonly type: "object";
115
208
  readonly additionalProperties: false;
116
- readonly required: readonly ["source", "image"];
209
+ readonly required: readonly ["source", "content"];
117
210
  readonly properties: {
118
211
  readonly source: {
119
212
  readonly type: "string";
120
213
  readonly minLength: 1;
121
214
  readonly maxLength: 2048;
122
215
  };
123
- readonly image: {
216
+ readonly content: {
124
217
  readonly type: "string";
125
218
  readonly pattern: "^data:image/png;base64,[A-Za-z0-9+/]*={0,2}$";
126
219
  readonly maxLength: number;
@@ -128,5 +221,27 @@ export declare const documentSchema: {
128
221
  };
129
222
  };
130
223
  };
224
+ readonly thumbnail: {
225
+ readonly type: "object";
226
+ readonly additionalProperties: false;
227
+ readonly required: readonly ["width", "height", "content"];
228
+ readonly properties: {
229
+ readonly width: {
230
+ readonly type: "integer";
231
+ readonly minimum: 1;
232
+ readonly maximum: 200;
233
+ };
234
+ readonly height: {
235
+ readonly type: "integer";
236
+ readonly minimum: 1;
237
+ readonly maximum: 200;
238
+ };
239
+ readonly content: {
240
+ readonly type: "string";
241
+ readonly pattern: "^data:image/png;base64,[A-Za-z0-9+/]*={0,2}$";
242
+ readonly maxLength: number;
243
+ };
244
+ };
245
+ };
131
246
  };
132
247
  };
@@ -1,17 +1,25 @@
1
+ import { UUID_PATTERN, authorDetailsSchema, fileSchemaUrlSchema } from "../assets/schema.js";
2
+ import { SCHEMA_DIALECT, schemaUrl } from "../schemas/index.js";
1
3
  // src/lib/document/schema.ts
2
- /** The `format` of a PaintCanvas document */
3
- var DOCUMENT_FORMAT = "paintcanvas-document";
4
- /** Version of the document format */
5
- var DOCUMENT_VERSION = 1;
4
+ /** Version of the document schema */
5
+ var DOCUMENT_SCHEMA_VERSION = "1.0.0";
6
+ /** `@type` of a document */
7
+ var DOCUMENT_TYPE = "PaintCanvas/Document";
6
8
  /** Largest canvas width or height a document may have */
7
9
  var MAX_DOCUMENT_SIZE = 1e4;
8
10
  /** Most layers in a document (a canvas may allow fewer, see `maxLayers`) */
9
11
  var MAX_DOCUMENT_LAYERS = 100;
10
12
  /** Most drawings from elsewhere in a document */
11
13
  var MAX_DOCUMENT_SHEETS = 100;
12
- /** Longest image in a document (characters): a PNG data URL of up to 100 MB */
14
+ /** Most authors of a document */
15
+ var MAX_DOCUMENT_AUTHORS = 100;
16
+ /** Longest document or layer name (characters) */
17
+ var MAX_DOCUMENT_NAME_LENGTH = 100;
18
+ /** Longest side of a document's thumbnail (pixels) */
19
+ var THUMBNAIL_SIZE = 200;
20
+ /** Longest image in a document file (characters): a PNG data URL of up to 100 MB */
13
21
  var MAX_DOCUMENT_IMAGE_LENGTH = Math.ceil(104857600 / 3) * 4 + 22;
14
- /** Blend modes a layer may have (see `BlendMode`) */
22
+ /** Blending modes a layer may have (see `BlendMode`) */
15
23
  var DOCUMENT_BLEND_MODES = [
16
24
  "source-over",
17
25
  "screen",
@@ -20,6 +28,7 @@ var DOCUMENT_BLEND_MODES = [
20
28
  "darken",
21
29
  "overlay"
22
30
  ];
31
+ /** A PNG data URL */
23
32
  var image = {
24
33
  type: "string",
25
34
  pattern: "^data:image/png;base64,[A-Za-z0-9+/]*={0,2}$",
@@ -30,14 +39,33 @@ var size = {
30
39
  minimum: 1,
31
40
  maximum: MAX_DOCUMENT_SIZE
32
41
  };
33
- /** JSON schema for a document */
42
+ var name = {
43
+ type: "string",
44
+ minLength: 1,
45
+ maxLength: 100,
46
+ pattern: "\\S"
47
+ };
48
+ /** Matches an ISO 8601 date and time, e.g. `2026-11-11T09:30:00.000Z` */
49
+ var DATE_TIME_PATTERN = "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2})$";
50
+ var date = {
51
+ type: "string",
52
+ pattern: DATE_TIME_PATTERN
53
+ };
54
+ /** JSON schema for a document file */
34
55
  var documentSchema = {
35
- $id: "paintcanvas/document",
56
+ $schema: SCHEMA_DIALECT,
57
+ $id: schemaUrl("document", DOCUMENT_SCHEMA_VERSION),
58
+ "@type": DOCUMENT_TYPE,
36
59
  type: "object",
37
60
  additionalProperties: false,
38
61
  required: [
39
- "format",
40
- "version",
62
+ "$schema",
63
+ "@type",
64
+ "id",
65
+ "name",
66
+ "author",
67
+ "dateCreated",
68
+ "dateOpened",
41
69
  "width",
42
70
  "height",
43
71
  "layers",
@@ -45,8 +73,20 @@ var documentSchema = {
45
73
  "external"
46
74
  ],
47
75
  properties: {
48
- format: { const: DOCUMENT_FORMAT },
49
- version: { const: 1 },
76
+ $schema: fileSchemaUrlSchema,
77
+ "@type": { const: DOCUMENT_TYPE },
78
+ id: {
79
+ type: "string",
80
+ pattern: UUID_PATTERN
81
+ },
82
+ name,
83
+ author: {
84
+ type: "array",
85
+ maxItems: 100,
86
+ items: authorDetailsSchema
87
+ },
88
+ dateCreated: date,
89
+ dateOpened: date,
50
90
  width: size,
51
91
  height: size,
52
92
  layers: {
@@ -57,19 +97,23 @@ var documentSchema = {
57
97
  type: "object",
58
98
  additionalProperties: false,
59
99
  required: [
100
+ "id",
60
101
  "name",
61
102
  "visible",
62
103
  "opacity",
63
- "blendMode",
104
+ "blendingMode",
64
105
  "alphaLock",
65
- "image"
106
+ "content"
66
107
  ],
67
108
  properties: {
68
- name: {
109
+ id: {
69
110
  type: "string",
70
111
  minLength: 1,
71
- maxLength: 64,
72
- pattern: "\\S"
112
+ maxLength: 100
113
+ },
114
+ name: {
115
+ ...name,
116
+ maxLength: 64
73
117
  },
74
118
  visible: { type: "boolean" },
75
119
  opacity: {
@@ -77,16 +121,16 @@ var documentSchema = {
77
121
  minimum: 0,
78
122
  maximum: 1
79
123
  },
80
- blendMode: { enum: DOCUMENT_BLEND_MODES },
124
+ blendingMode: { enum: DOCUMENT_BLEND_MODES },
81
125
  alphaLock: { type: "boolean" },
82
- image
126
+ content: image
83
127
  }
84
128
  }
85
129
  },
86
130
  activeLayer: {
87
- type: "integer",
88
- minimum: 0,
89
- maximum: 99
131
+ type: "string",
132
+ minLength: 1,
133
+ maxLength: 100
90
134
  },
91
135
  external: {
92
136
  type: "array",
@@ -94,17 +138,39 @@ var documentSchema = {
94
138
  items: {
95
139
  type: "object",
96
140
  additionalProperties: false,
97
- required: ["source", "image"],
141
+ required: ["source", "content"],
98
142
  properties: {
99
143
  source: {
100
144
  type: "string",
101
145
  minLength: 1,
102
146
  maxLength: 2048
103
147
  },
104
- image
148
+ content: image
105
149
  }
106
150
  }
151
+ },
152
+ thumbnail: {
153
+ type: "object",
154
+ additionalProperties: false,
155
+ required: [
156
+ "width",
157
+ "height",
158
+ "content"
159
+ ],
160
+ properties: {
161
+ width: {
162
+ type: "integer",
163
+ minimum: 1,
164
+ maximum: 200
165
+ },
166
+ height: {
167
+ type: "integer",
168
+ minimum: 1,
169
+ maximum: 200
170
+ },
171
+ content: image
172
+ }
107
173
  }
108
174
  }
109
175
  };
110
- export { DOCUMENT_BLEND_MODES, DOCUMENT_FORMAT, DOCUMENT_VERSION, MAX_DOCUMENT_IMAGE_LENGTH, MAX_DOCUMENT_LAYERS, MAX_DOCUMENT_SHEETS, MAX_DOCUMENT_SIZE, documentSchema };
176
+ export { DATE_TIME_PATTERN, DOCUMENT_BLEND_MODES, DOCUMENT_SCHEMA_VERSION, DOCUMENT_TYPE, MAX_DOCUMENT_AUTHORS, MAX_DOCUMENT_IMAGE_LENGTH, MAX_DOCUMENT_LAYERS, MAX_DOCUMENT_NAME_LENGTH, MAX_DOCUMENT_SHEETS, MAX_DOCUMENT_SIZE, THUMBNAIL_SIZE, documentSchema };