js-draw 1.2.1 → 1.3.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 (122) hide show
  1. package/README.md +30 -30
  2. package/dist/Editor.css +70 -4
  3. package/dist/bundle.js +2 -2
  4. package/dist/bundledStyles.js +1 -1
  5. package/dist/cjs/Editor.d.ts +73 -40
  6. package/dist/cjs/Editor.js +90 -24
  7. package/dist/cjs/EditorImage.d.ts +58 -6
  8. package/dist/cjs/EditorImage.js +336 -60
  9. package/dist/cjs/SVGLoader.d.ts +10 -4
  10. package/dist/cjs/SVGLoader.js +30 -10
  11. package/dist/cjs/UndoRedoHistory.d.ts +2 -2
  12. package/dist/cjs/UndoRedoHistory.js +4 -2
  13. package/dist/cjs/Viewport.d.ts +2 -1
  14. package/dist/cjs/Viewport.js +12 -3
  15. package/dist/cjs/commands/Command.d.ts +1 -0
  16. package/dist/cjs/commands/Command.js +1 -0
  17. package/dist/cjs/commands/Erase.js +1 -1
  18. package/dist/cjs/commands/SerializableCommand.d.ts +1 -1
  19. package/dist/cjs/commands/SerializableCommand.js +16 -2
  20. package/dist/cjs/commands/localization.d.ts +2 -0
  21. package/dist/cjs/commands/localization.js +2 -0
  22. package/dist/cjs/components/AbstractComponent.d.ts +38 -0
  23. package/dist/cjs/components/AbstractComponent.js +31 -0
  24. package/dist/cjs/components/BackgroundComponent.d.ts +10 -1
  25. package/dist/cjs/components/BackgroundComponent.js +60 -6
  26. package/dist/cjs/components/SVGGlobalAttributesObject.d.ts +2 -1
  27. package/dist/cjs/components/SVGGlobalAttributesObject.js +30 -1
  28. package/dist/cjs/components/Stroke.d.ts +1 -0
  29. package/dist/cjs/components/Stroke.js +44 -0
  30. package/dist/cjs/components/UnknownSVGObject.d.ts +2 -1
  31. package/dist/cjs/components/UnknownSVGObject.js +30 -1
  32. package/dist/cjs/components/lib.d.ts +2 -2
  33. package/dist/cjs/components/lib.js +15 -2
  34. package/dist/cjs/lib.d.ts +2 -45
  35. package/dist/cjs/lib.js +2 -45
  36. package/dist/cjs/rendering/RenderablePathSpec.d.ts +1 -0
  37. package/dist/cjs/rendering/RenderablePathSpec.js +1 -0
  38. package/dist/cjs/rendering/RenderingStyle.d.ts +1 -0
  39. package/dist/cjs/rendering/lib.d.ts +1 -0
  40. package/dist/cjs/rendering/lib.js +5 -1
  41. package/dist/cjs/rendering/renderers/AbstractRenderer.js +1 -1
  42. package/dist/cjs/shortcuts/KeyboardShortcutManager.d.ts +2 -2
  43. package/dist/cjs/shortcuts/KeyboardShortcutManager.js +2 -2
  44. package/dist/cjs/toolbar/localization.d.ts +1 -0
  45. package/dist/cjs/toolbar/localization.js +1 -0
  46. package/dist/cjs/toolbar/widgets/BaseWidget.js +5 -0
  47. package/dist/cjs/toolbar/widgets/DocumentPropertiesWidget.js +54 -25
  48. package/dist/cjs/toolbar/widgets/components/makeGridSelector.js +8 -0
  49. package/dist/cjs/tools/PanZoom.js +13 -8
  50. package/dist/cjs/tools/ScrollbarTool.d.ts +18 -0
  51. package/dist/cjs/tools/ScrollbarTool.js +85 -0
  52. package/dist/cjs/tools/SelectionTool/SelectionTool.selecting.test.d.ts +1 -0
  53. package/dist/cjs/tools/ToolController.js +2 -0
  54. package/dist/cjs/types.d.ts +3 -1
  55. package/dist/cjs/util/adjustEditorThemeForContrast.js +1 -0
  56. package/dist/cjs/util/assertions.d.ts +4 -0
  57. package/dist/cjs/util/assertions.js +12 -1
  58. package/dist/cjs/version.js +1 -1
  59. package/dist/mjs/Editor.d.ts +73 -40
  60. package/dist/mjs/Editor.mjs +90 -24
  61. package/dist/mjs/EditorImage.d.ts +58 -6
  62. package/dist/mjs/EditorImage.mjs +313 -61
  63. package/dist/mjs/SVGLoader.d.ts +10 -4
  64. package/dist/mjs/SVGLoader.mjs +29 -9
  65. package/dist/mjs/UndoRedoHistory.d.ts +2 -2
  66. package/dist/mjs/UndoRedoHistory.mjs +4 -2
  67. package/dist/mjs/Viewport.d.ts +2 -1
  68. package/dist/mjs/Viewport.mjs +12 -3
  69. package/dist/mjs/commands/Command.d.ts +1 -0
  70. package/dist/mjs/commands/Command.mjs +1 -0
  71. package/dist/mjs/commands/Erase.mjs +1 -1
  72. package/dist/mjs/commands/SerializableCommand.d.ts +1 -1
  73. package/dist/mjs/commands/SerializableCommand.mjs +16 -2
  74. package/dist/mjs/commands/localization.d.ts +2 -0
  75. package/dist/mjs/commands/localization.mjs +2 -0
  76. package/dist/mjs/components/AbstractComponent.d.ts +38 -0
  77. package/dist/mjs/components/AbstractComponent.mjs +30 -0
  78. package/dist/mjs/components/BackgroundComponent.d.ts +10 -1
  79. package/dist/mjs/components/BackgroundComponent.mjs +37 -6
  80. package/dist/mjs/components/SVGGlobalAttributesObject.d.ts +2 -1
  81. package/dist/mjs/components/SVGGlobalAttributesObject.mjs +7 -1
  82. package/dist/mjs/components/Stroke.d.ts +1 -0
  83. package/dist/mjs/components/Stroke.mjs +44 -0
  84. package/dist/mjs/components/UnknownSVGObject.d.ts +2 -1
  85. package/dist/mjs/components/UnknownSVGObject.mjs +7 -1
  86. package/dist/mjs/components/lib.d.ts +2 -2
  87. package/dist/mjs/components/lib.mjs +2 -2
  88. package/dist/mjs/lib.d.ts +2 -45
  89. package/dist/mjs/lib.mjs +2 -45
  90. package/dist/mjs/rendering/RenderablePathSpec.d.ts +1 -0
  91. package/dist/mjs/rendering/RenderablePathSpec.mjs +1 -0
  92. package/dist/mjs/rendering/RenderingStyle.d.ts +1 -0
  93. package/dist/mjs/rendering/lib.d.ts +1 -0
  94. package/dist/mjs/rendering/lib.mjs +1 -0
  95. package/dist/mjs/rendering/renderers/AbstractRenderer.mjs +1 -1
  96. package/dist/mjs/shortcuts/KeyboardShortcutManager.d.ts +2 -2
  97. package/dist/mjs/shortcuts/KeyboardShortcutManager.mjs +2 -2
  98. package/dist/mjs/toolbar/localization.d.ts +1 -0
  99. package/dist/mjs/toolbar/localization.mjs +1 -0
  100. package/dist/mjs/toolbar/widgets/BaseWidget.mjs +5 -0
  101. package/dist/mjs/toolbar/widgets/DocumentPropertiesWidget.mjs +54 -25
  102. package/dist/mjs/toolbar/widgets/components/makeGridSelector.mjs +8 -0
  103. package/dist/mjs/tools/PanZoom.mjs +13 -8
  104. package/dist/mjs/tools/ScrollbarTool.d.ts +18 -0
  105. package/dist/mjs/tools/ScrollbarTool.mjs +79 -0
  106. package/dist/mjs/tools/SelectionTool/SelectionTool.selecting.test.d.ts +1 -0
  107. package/dist/mjs/tools/ToolController.mjs +2 -0
  108. package/dist/mjs/types.d.ts +3 -1
  109. package/dist/mjs/util/adjustEditorThemeForContrast.mjs +1 -0
  110. package/dist/mjs/util/assertions.d.ts +4 -0
  111. package/dist/mjs/util/assertions.mjs +10 -0
  112. package/dist/mjs/version.mjs +1 -1
  113. package/package.json +3 -4
  114. package/src/Editor.scss +8 -0
  115. package/src/dialogs/dialogs.scss +2 -1
  116. package/src/toolbar/AbstractToolbar.scss +3 -0
  117. package/src/toolbar/EdgeToolbar.scss +4 -1
  118. package/src/toolbar/widgets/DocumentPropertiesWidget.scss +12 -0
  119. package/src/toolbar/widgets/components/makeGridSelector.scss +6 -1
  120. package/src/tools/ScrollbarTool.scss +57 -0
  121. package/src/tools/{SoundUITool.css → SoundUITool.scss} +4 -0
  122. package/src/tools/tools.scss +2 -1
@@ -4,22 +4,28 @@ export declare const defaultSVGViewRect: Rect2;
4
4
  export declare const svgAttributesDataKey = "svgAttrs";
5
5
  export declare const svgStyleAttributesDataKey = "svgStyleAttrs";
6
6
  export declare const svgLoaderAttributeContainerID = "svgContainerID";
7
+ export declare const svgLoaderAutoresizeClassName = "js-draw--autoresize";
7
8
  export type SVGLoaderUnknownAttribute = [string, string];
8
9
  export type SVGLoaderUnknownStyleAttribute = {
9
10
  key: string;
10
11
  value: string;
11
12
  priority?: string;
12
13
  };
14
+ export interface SVGLoaderOptions {
15
+ sanitize?: boolean;
16
+ disableUnknownObjectWarnings?: boolean;
17
+ }
13
18
  export default class SVGLoader implements ImageLoader {
14
19
  private source;
15
- private onFinish?;
16
- private readonly storeUnknown;
20
+ private onFinish;
17
21
  private onAddComponent;
18
22
  private onProgress;
19
23
  private onDetermineExportRect;
20
24
  private processedCount;
21
25
  private totalToProcess;
22
26
  private rootViewBox;
27
+ private readonly storeUnknown;
28
+ private readonly disableUnknownObjectWarnings;
23
29
  private constructor();
24
30
  private getStyle;
25
31
  private strokeDataFromElem;
@@ -48,7 +54,7 @@ export default class SVGLoader implements ImageLoader {
48
54
  *
49
55
  * @see {@link Editor.loadFrom}
50
56
  * @param text - Textual representation of the SVG (e.g. `<svg viewbox='...'>...</svg>`).
51
- * @param sanitize - if `true`, don't store unknown attributes.
57
+ * @param options - if `true` or `false`, treated as the `sanitize` option -- don't store unknown attributes.
52
58
  */
53
- static fromString(text: string, sanitize?: boolean): SVGLoader;
59
+ static fromString(text: string, options?: Partial<SVGLoaderOptions> | boolean): SVGLoader;
54
60
  }
@@ -15,13 +15,15 @@ export const svgStyleAttributesDataKey = 'svgStyleAttrs';
15
15
  // Key that specifies the ID of an SVG element that contained a given node when the image
16
16
  // was first loaded.
17
17
  export const svgLoaderAttributeContainerID = 'svgContainerID';
18
+ // If present in the exported SVG's class list, the image will be
19
+ // autoresized when components are added/removed.
20
+ export const svgLoaderAutoresizeClassName = 'js-draw--autoresize';
18
21
  const supportedStrokeFillStyleAttrs = ['stroke', 'fill', 'stroke-width'];
19
22
  // Handles loading images from SVG.
20
23
  export default class SVGLoader {
21
- constructor(source, onFinish, storeUnknown = true) {
24
+ constructor(source, onFinish, options) {
22
25
  this.source = source;
23
26
  this.onFinish = onFinish;
24
- this.storeUnknown = storeUnknown;
25
27
  this.onAddComponent = null;
26
28
  this.onProgress = null;
27
29
  this.onDetermineExportRect = null;
@@ -29,6 +31,8 @@ export default class SVGLoader {
29
31
  this.totalToProcess = 0;
30
32
  this.containerGroupIDs = [];
31
33
  this.encounteredIDs = [];
34
+ this.storeUnknown = !(options.sanitize ?? false);
35
+ this.disableUnknownObjectWarnings = !!options.disableUnknownObjectWarnings;
32
36
  }
33
37
  // If [computedStyles] is given, it is preferred to directly accessing node's style object.
34
38
  getStyle(node, computedStyles) {
@@ -395,8 +399,9 @@ export default class SVGLoader {
395
399
  console.warn(`node ${node} has an unparsable viewbox. Viewbox: ${viewBoxAttr}. Match: ${components}.`);
396
400
  return;
397
401
  }
402
+ const autoresize = node.classList.contains(svgLoaderAutoresizeClassName);
398
403
  this.rootViewBox = new Rect2(x, y, width, height);
399
- this.onDetermineExportRect?.(this.rootViewBox);
404
+ this.onDetermineExportRect?.(this.rootViewBox, { autoresize });
400
405
  }
401
406
  async updateSVGAttrs(node) {
402
407
  if (this.storeUnknown) {
@@ -442,9 +447,11 @@ export default class SVGLoader {
442
447
  await this.addUnknownNode(node);
443
448
  break;
444
449
  default:
445
- console.warn('Unknown SVG element,', node, node.tagName);
446
- if (!(node instanceof SVGElement)) {
447
- console.warn('Element', node, 'is not an SVGElement!', this.storeUnknown ? 'Continuing anyway.' : 'Skipping.');
450
+ if (!this.disableUnknownObjectWarnings) {
451
+ console.warn('Unknown SVG element,', node, node.tagName);
452
+ if (!(node instanceof SVGElement)) {
453
+ console.warn('Element', node, 'is not an SVGElement!', this.storeUnknown ? 'Continuing anyway.' : 'Skipping.');
454
+ }
448
455
  }
449
456
  await this.addUnknownNode(node);
450
457
  return;
@@ -488,9 +495,9 @@ export default class SVGLoader {
488
495
  *
489
496
  * @see {@link Editor.loadFrom}
490
497
  * @param text - Textual representation of the SVG (e.g. `<svg viewbox='...'>...</svg>`).
491
- * @param sanitize - if `true`, don't store unknown attributes.
498
+ * @param options - if `true` or `false`, treated as the `sanitize` option -- don't store unknown attributes.
492
499
  */
493
- static fromString(text, sanitize = false) {
500
+ static fromString(text, options = false) {
494
501
  const sandbox = document.createElement('iframe');
495
502
  sandbox.src = 'about:blank';
496
503
  sandbox.setAttribute('sandbox', 'allow-same-origin');
@@ -528,9 +535,22 @@ export default class SVGLoader {
528
535
  const svgElem = sandboxDoc.createElementNS('http://www.w3.org/2000/svg', 'svg');
529
536
  svgElem.innerHTML = text;
530
537
  sandboxDoc.body.appendChild(svgElem);
538
+ // Handle options
539
+ let sanitize;
540
+ let disableUnknownObjectWarnings;
541
+ if (typeof options === 'boolean') {
542
+ sanitize = options;
543
+ disableUnknownObjectWarnings = false;
544
+ }
545
+ else {
546
+ sanitize = options.sanitize ?? false;
547
+ disableUnknownObjectWarnings = options.disableUnknownObjectWarnings ?? false;
548
+ }
531
549
  return new SVGLoader(svgElem, () => {
532
550
  svgElem.remove();
533
551
  sandbox.remove();
534
- }, !sanitize);
552
+ }, {
553
+ sanitize, disableUnknownObjectWarnings,
554
+ });
535
555
  }
536
556
  }
@@ -11,8 +11,8 @@ declare class UndoRedoHistory {
11
11
  constructor(editor: Editor, announceRedoCallback: AnnounceRedoCallback, announceUndoCallback: AnnounceUndoCallback);
12
12
  private fireUpdateEvent;
13
13
  push(command: Command, apply?: boolean): void;
14
- undo(): void;
15
- redo(): void;
14
+ undo(): void | Promise<void>;
15
+ redo(): void | Promise<void>;
16
16
  get undoStackSize(): number;
17
17
  get redoStackSize(): number;
18
18
  }
@@ -58,26 +58,28 @@ class UndoRedoHistory {
58
58
  const command = __classPrivateFieldGet(this, _UndoRedoHistory_undoStack, "f").pop();
59
59
  if (command) {
60
60
  __classPrivateFieldGet(this, _UndoRedoHistory_redoStack, "f").push(command);
61
- command.unapply(this.editor);
61
+ const result = command.unapply(this.editor);
62
62
  this.announceUndoCallback(command);
63
63
  this.fireUpdateEvent(UndoEventType.CommandUndone, command);
64
64
  this.editor.notifier.dispatch(EditorEventType.CommandUndone, {
65
65
  kind: EditorEventType.CommandUndone,
66
66
  command,
67
67
  });
68
+ return result;
68
69
  }
69
70
  }
70
71
  redo() {
71
72
  const command = __classPrivateFieldGet(this, _UndoRedoHistory_redoStack, "f").pop();
72
73
  if (command) {
73
74
  __classPrivateFieldGet(this, _UndoRedoHistory_undoStack, "f").push(command);
74
- command.apply(this.editor);
75
+ const result = command.apply(this.editor);
75
76
  this.announceRedoCallback(command);
76
77
  this.fireUpdateEvent(UndoEventType.CommandRedone, command);
77
78
  this.editor.notifier.dispatch(EditorEventType.CommandDone, {
78
79
  kind: EditorEventType.CommandDone,
79
80
  command,
80
81
  });
82
+ return result;
81
83
  }
82
84
  }
83
85
  get undoStackSize() {
@@ -18,6 +18,7 @@ export declare class Viewport {
18
18
  * useful when rendering with a temporarily different viewport.
19
19
  */
20
20
  getTemporaryClone(): Viewport;
21
+ /** Resizes the screen rect to the given size. @internal */
21
22
  updateScreenSize(screenSize: Vec2): void;
22
23
  /** Get the screen's visible region transformed into canvas space. */
23
24
  get visibleRect(): Rect2;
@@ -34,7 +35,7 @@ export declare class Viewport {
34
35
  resetTransform(newTransform?: Mat33): void;
35
36
  get screenToCanvasTransform(): Mat33;
36
37
  get canvasToScreenTransform(): Mat33;
37
- /** @returns the size of the visible region in pixels. */
38
+ /** @returns the size of the visible region in pixels (screen units). */
38
39
  getScreenRectSize(): Vec2;
39
40
  /** Alias for `getScreenRectSize`. @deprecated */
40
41
  getResolution(): Vec3;
@@ -32,7 +32,7 @@ export class Viewport {
32
32
  result.screenRect = this.screenRect;
33
33
  return result;
34
34
  }
35
- // @internal
35
+ /** Resizes the screen rect to the given size. @internal */
36
36
  updateScreenSize(screenSize) {
37
37
  this.screenRect = this.screenRect.resizedTo(screenSize);
38
38
  }
@@ -68,7 +68,7 @@ export class Viewport {
68
68
  get canvasToScreenTransform() {
69
69
  return this.transform;
70
70
  }
71
- /** @returns the size of the visible region in pixels. */
71
+ /** @returns the size of the visible region in pixels (screen units). */
72
72
  getScreenRectSize() {
73
73
  return this.screenRect.size;
74
74
  }
@@ -150,8 +150,17 @@ export class Viewport {
150
150
  // Computes and returns an affine transformation that makes `toMakeVisible` visible and roughly centered on the screen.
151
151
  computeZoomToTransform(toMakeVisible, allowZoomIn = true, allowZoomOut = true) {
152
152
  let transform = Mat33.identity;
153
+ // Invalid size? (Would divide by zero)
153
154
  if (toMakeVisible.w === 0 || toMakeVisible.h === 0) {
154
- throw new Error(`${toMakeVisible.toString()} rectangle is empty! Cannot zoom to!`);
155
+ // Create a new rectangle with a valid size
156
+ let newSize = Math.max(toMakeVisible.w, toMakeVisible.h);
157
+ // Choose a reasonable default size, but don't zoom.
158
+ if (newSize === 0) {
159
+ newSize = 50;
160
+ allowZoomIn = false;
161
+ allowZoomOut = false;
162
+ }
163
+ toMakeVisible = new Rect2(toMakeVisible.x, toMakeVisible.y, newSize, newSize);
155
164
  }
156
165
  if (isNaN(toMakeVisible.size.magnitude())) {
157
166
  throw new Error(`${toMakeVisible.toString()} rectangle has NaN size! Cannot zoom to!`);
@@ -5,6 +5,7 @@ export declare abstract class Command {
5
5
  abstract unapply(editor: Editor): Promise<void> | void;
6
6
  onDrop(_editor: Editor): void;
7
7
  abstract description(editor: Editor, localizationTable: EditorLocalization): string;
8
+ /** @deprecated Use {@link uniteCommands} */
8
9
  static union(a: Command, b: Command): Command;
9
10
  static readonly empty: {
10
11
  description(_editor: Editor, _localizationTable: EditorLocalization): string;
@@ -1,6 +1,7 @@
1
1
  export class Command {
2
2
  // Called when the command is being deleted
3
3
  onDrop(_editor) { }
4
+ /** @deprecated Use {@link uniteCommands} */
4
5
  static union(a, b) {
5
6
  return new class extends Command {
6
7
  apply(editor) {
@@ -32,8 +32,8 @@ class Erase extends SerializableCommand {
32
32
  for (const part of this.toRemove) {
33
33
  const parent = editor.image.findParent(part);
34
34
  if (parent) {
35
- editor.image.onDestroyElement(part);
36
35
  parent.remove();
36
+ editor.image.onDestroyElement(part);
37
37
  }
38
38
  }
39
39
  this.applied = true;
@@ -2,7 +2,7 @@ import Editor from '../Editor';
2
2
  import Command from './Command';
3
3
  export type DeserializationCallback = (data: Record<string, any> | any[], editor: Editor) => SerializableCommand;
4
4
  export default abstract class SerializableCommand extends Command {
5
- private commandTypeId;
5
+ #private;
6
6
  constructor(commandTypeId: string);
7
7
  protected abstract serializeToJSON(): string | Record<string, any> | any[];
8
8
  private static deserializationCallbacks;
@@ -1,11 +1,24 @@
1
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
2
+ if (kind === "m") throw new TypeError("Private method is not writable");
3
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
4
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
5
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6
+ };
7
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
+ };
12
+ var _SerializableCommand_commandTypeId;
1
13
  import Command from './Command.mjs';
2
14
  class SerializableCommand extends Command {
3
15
  constructor(commandTypeId) {
4
16
  super();
5
- this.commandTypeId = commandTypeId;
17
+ _SerializableCommand_commandTypeId.set(this, void 0);
6
18
  if (!(commandTypeId in SerializableCommand.deserializationCallbacks)) {
7
19
  throw new Error(`Command ${commandTypeId} must have a registered deserialization callback. To do this, call SerializableCommand.register.`);
8
20
  }
21
+ __classPrivateFieldSet(this, _SerializableCommand_commandTypeId, commandTypeId, "f");
9
22
  }
10
23
  // Convert this command to an object that can be passed to `JSON.stringify`.
11
24
  //
@@ -14,7 +27,7 @@ class SerializableCommand extends Command {
14
27
  serialize() {
15
28
  return {
16
29
  data: this.serializeToJSON(),
17
- commandType: this.commandTypeId,
30
+ commandType: __classPrivateFieldGet(this, _SerializableCommand_commandTypeId, "f"),
18
31
  };
19
32
  }
20
33
  // Convert a `string` containing JSON data (or the output of `JSON.parse`) into a
@@ -33,5 +46,6 @@ class SerializableCommand extends Command {
33
46
  SerializableCommand.deserializationCallbacks[commandTypeId] = deserialize;
34
47
  }
35
48
  }
49
+ _SerializableCommand_commandTypeId = new WeakMap();
36
50
  SerializableCommand.deserializationCallbacks = {};
37
51
  export default SerializableCommand;
@@ -13,6 +13,8 @@ export interface CommandLocalization {
13
13
  updatedViewport: string;
14
14
  transformedElements: (elemCount: number) => string;
15
15
  resizeOutputCommand: (newSize: Rect2) => string;
16
+ enabledAutoresizeOutputCommand: string;
17
+ disabledAutoresizeOutputCommand: string;
16
18
  addElementAction: (elemDescription: string) => string;
17
19
  eraseAction: (elemDescription: string, numElems: number) => string;
18
20
  duplicateAction: (elemDescription: string, count: number) => string;
@@ -2,6 +2,8 @@ export const defaultCommandLocalization = {
2
2
  updatedViewport: 'Transformed Viewport',
3
3
  transformedElements: (elemCount) => `Transformed ${elemCount} element${elemCount === 1 ? '' : 's'}`,
4
4
  resizeOutputCommand: (newSize) => `Resized image to ${newSize.w}x${newSize.h}`,
5
+ enabledAutoresizeOutputCommand: 'Enabled output autoresize',
6
+ disabledAutoresizeOutputCommand: 'Disabled output autoresize',
5
7
  addElementAction: (componentDescription) => `Added ${componentDescription}`,
6
8
  eraseAction: (componentDescription, numElems) => `Erased ${numElems} ${componentDescription}`,
7
9
  duplicateAction: (componentDescription, numElems) => `Duplicated ${numElems} ${componentDescription}`,
@@ -6,12 +6,41 @@ import { ImageComponentLocalization } from './localization';
6
6
  export type LoadSaveData = (string[] | Record<symbol, string | number>);
7
7
  export type LoadSaveDataTable = Record<string, Array<LoadSaveData>>;
8
8
  export type DeserializeCallback = (data: string) => AbstractComponent;
9
+ export declare enum ComponentSizingMode {
10
+ /** The default. The compnent gets its size from its bounding box. */
11
+ BoundingBox = 0,
12
+ /** Causes the component to fill the entire visible region of the screen */
13
+ FillScreen = 1,
14
+ /**
15
+ * Displays the component anywhere (arbitrary location) on the
16
+ * canvas. (Ignoring the bounding box).
17
+ *
18
+ * These components may be ignored unless a full render is done.
19
+ *
20
+ * Intended for compnents that need to be rendered on a full export,
21
+ * but won't be visible to the user.
22
+ *
23
+ * For example, a metadata component.
24
+ */
25
+ Anywhere = 2
26
+ }
9
27
  /**
10
28
  * A base class for everything that can be added to an {@link EditorImage}.
11
29
  */
12
30
  export default abstract class AbstractComponent {
13
31
  private readonly componentKind;
14
32
  protected lastChangedTime: number;
33
+ /**
34
+ * The bounding box of this component.
35
+ * {@link getBBox}, by default, returns `contentBBox`.
36
+ * This must be set by components.
37
+ *
38
+ * If this changes, {@link EditorImage.queueRerenderOf} should be called for
39
+ * this object (provided that this object has been added to the editor.)
40
+ *
41
+ * **Note**: This value is ignored if {@link getSizingMode} returns `FillScreen`
42
+ * or `FillImage`.
43
+ */
15
44
  protected abstract contentBBox: Rect2;
16
45
  private zIndex;
17
46
  private id;
@@ -39,6 +68,15 @@ export default abstract class AbstractComponent {
39
68
  * @returns the bounding box of this. Unlike `getBBox`, this should **not** be a rough estimate.
40
69
  */
41
70
  getExactBBox(): Rect2;
71
+ /**
72
+ * Returns information about how this component should be displayed
73
+ * (e.g. fill the screen or get its size from {@link getBBox}).
74
+ *
75
+ * {@link EditorImage.queueRerenderOf} must be called to apply changes to
76
+ * the output of this method if this component has already been added to an
77
+ * {@link EditorImage}.
78
+ */
79
+ getSizingMode(): ComponentSizingMode;
42
80
  /** Called when this component is added to the given image. */
43
81
  onAddToImage(_image: EditorImage): void;
44
82
  onRemoveFromImage(): void;
@@ -7,6 +7,25 @@ import SerializableCommand from '../commands/SerializableCommand.mjs';
7
7
  import EditorImage from '../EditorImage.mjs';
8
8
  import { Mat33 } from '@js-draw/math';
9
9
  import UnresolvedSerializableCommand from '../commands/UnresolvedCommand.mjs';
10
+ export var ComponentSizingMode;
11
+ (function (ComponentSizingMode) {
12
+ /** The default. The compnent gets its size from its bounding box. */
13
+ ComponentSizingMode[ComponentSizingMode["BoundingBox"] = 0] = "BoundingBox";
14
+ /** Causes the component to fill the entire visible region of the screen */
15
+ ComponentSizingMode[ComponentSizingMode["FillScreen"] = 1] = "FillScreen";
16
+ /**
17
+ * Displays the component anywhere (arbitrary location) on the
18
+ * canvas. (Ignoring the bounding box).
19
+ *
20
+ * These components may be ignored unless a full render is done.
21
+ *
22
+ * Intended for compnents that need to be rendered on a full export,
23
+ * but won't be visible to the user.
24
+ *
25
+ * For example, a metadata component.
26
+ */
27
+ ComponentSizingMode[ComponentSizingMode["Anywhere"] = 2] = "Anywhere";
28
+ })(ComponentSizingMode || (ComponentSizingMode = {}));
10
29
  /**
11
30
  * A base class for everything that can be added to an {@link EditorImage}.
12
31
  */
@@ -72,6 +91,17 @@ class AbstractComponent {
72
91
  getExactBBox() {
73
92
  return this.getBBox();
74
93
  }
94
+ /**
95
+ * Returns information about how this component should be displayed
96
+ * (e.g. fill the screen or get its size from {@link getBBox}).
97
+ *
98
+ * {@link EditorImage.queueRerenderOf} must be called to apply changes to
99
+ * the output of this method if this component has already been added to an
100
+ * {@link EditorImage}.
101
+ */
102
+ getSizingMode() {
103
+ return ComponentSizingMode.BoundingBox;
104
+ }
75
105
  /** Called when this component is added to the given image. */
76
106
  onAddToImage(_image) { }
77
107
  onRemoveFromImage() { }
@@ -3,7 +3,7 @@ import EditorImage from '../EditorImage';
3
3
  import SerializableCommand from '../commands/SerializableCommand';
4
4
  import { LineSegment2, Mat33, Rect2, Color4 } from '@js-draw/math';
5
5
  import AbstractRenderer from '../rendering/renderers/AbstractRenderer';
6
- import AbstractComponent from './AbstractComponent';
6
+ import AbstractComponent, { ComponentSizingMode } from './AbstractComponent';
7
7
  import { ImageComponentLocalization } from './localization';
8
8
  import RestyleableComponent, { ComponentStyle } from './RestylableComponent';
9
9
  export declare enum BackgroundType {
@@ -24,6 +24,8 @@ export default class BackgroundComponent extends AbstractComponent implements Re
24
24
  private mainColor;
25
25
  protected contentBBox: Rect2;
26
26
  private viewportSizeChangeListener;
27
+ private autoresizeChangedListener;
28
+ private fillsScreen;
27
29
  private gridSize;
28
30
  private gridStrokeWidth;
29
31
  private secondaryColor;
@@ -41,10 +43,17 @@ export default class BackgroundComponent extends AbstractComponent implements Re
41
43
  onRemoveFromImage(): void;
42
44
  private recomputeBBox;
43
45
  private generateGridPath;
46
+ /**
47
+ * @returns this background's bounding box if the screen size is taken into
48
+ * account (which may be necessary if this component is configured to fill the
49
+ * entire screen).
50
+ */
51
+ private getFullBoundingBox;
44
52
  render(canvas: AbstractRenderer, visibleRect?: Rect2): void;
45
53
  intersects(lineSegment: LineSegment2): boolean;
46
54
  isSelectable(): boolean;
47
55
  isBackground(): boolean;
56
+ getSizingMode(): ComponentSizingMode;
48
57
  protected serializeToJSON(): {
49
58
  mainColor: string;
50
59
  secondaryColor: string | undefined;
@@ -1,6 +1,6 @@
1
1
  import { EditorImageEventType } from '../EditorImage.mjs';
2
2
  import { Rect2, Color4, toRoundedString, Path, PathCommandType, Vec2 } from '@js-draw/math';
3
- import AbstractComponent from './AbstractComponent.mjs';
3
+ import AbstractComponent, { ComponentSizingMode } from './AbstractComponent.mjs';
4
4
  import { createRestyleComponentCommand } from './RestylableComponent.mjs';
5
5
  import Viewport from '../Viewport.mjs';
6
6
  import { pathToRenderable } from '../rendering/RenderablePathSpec.mjs';
@@ -28,6 +28,10 @@ export default class BackgroundComponent extends AbstractComponent {
28
28
  this.backgroundType = backgroundType;
29
29
  this.mainColor = mainColor;
30
30
  this.viewportSizeChangeListener = null;
31
+ this.autoresizeChangedListener = null;
32
+ // Whether the background should grow/shrink to match the screen size,
33
+ // rather than being clipped to the image boundaries.
34
+ this.fillsScreen = false;
31
35
  this.gridSize = Viewport.getGridSize(2);
32
36
  this.gridStrokeWidth = 0.7;
33
37
  this.secondaryColor = null;
@@ -102,22 +106,37 @@ export default class BackgroundComponent extends AbstractComponent {
102
106
  this.viewportSizeChangeListener = image.notifier.on(EditorImageEventType.ExportViewportChanged, () => {
103
107
  this.recomputeBBox(image);
104
108
  });
109
+ this.autoresizeChangedListener = image.notifier.on(EditorImageEventType.AutoresizeModeChanged, () => {
110
+ this.recomputeBBox(image);
111
+ });
105
112
  this.recomputeBBox(image);
106
113
  }
107
114
  onRemoveFromImage() {
108
115
  this.viewportSizeChangeListener?.remove();
116
+ this.autoresizeChangedListener?.remove();
109
117
  this.viewportSizeChangeListener = null;
118
+ this.autoresizeChangedListener = null;
110
119
  }
111
120
  recomputeBBox(image) {
112
121
  const importExportRect = image.getImportExportViewport().visibleRect;
122
+ let needsRerender = false;
113
123
  if (!this.contentBBox.eq(importExportRect)) {
114
124
  this.contentBBox = importExportRect;
115
- // Re-render this if already added to the EditorImage.
125
+ needsRerender = true;
126
+ }
127
+ const imageAutoresizes = image.getAutoresizeEnabled();
128
+ if (imageAutoresizes !== this.fillsScreen) {
129
+ this.fillsScreen = imageAutoresizes;
130
+ needsRerender = true;
131
+ }
132
+ if (needsRerender) {
133
+ // Re-renders this if already added to the EditorImage.
116
134
  image.queueRerenderOf(this);
117
135
  }
118
136
  }
119
137
  generateGridPath(visibleRect) {
120
- const targetRect = visibleRect?.grownBy(this.gridStrokeWidth)?.intersection(this.contentBBox) ?? this.contentBBox;
138
+ const contentBBox = this.getFullBoundingBox(visibleRect);
139
+ const targetRect = visibleRect?.grownBy(this.gridStrokeWidth)?.intersection(contentBBox) ?? contentBBox;
121
140
  const roundDownToGrid = (coord) => Math.floor(coord / this.gridSize) * this.gridSize;
122
141
  const roundUpToGrid = (coord) => Math.ceil(coord / this.gridSize) * this.gridSize;
123
142
  const startY = roundUpToGrid(targetRect.y);
@@ -158,24 +177,33 @@ export default class BackgroundComponent extends AbstractComponent {
158
177
  }
159
178
  return new Path(startPoint, result);
160
179
  }
180
+ /**
181
+ * @returns this background's bounding box if the screen size is taken into
182
+ * account (which may be necessary if this component is configured to fill the
183
+ * entire screen).
184
+ */
185
+ getFullBoundingBox(visibleRect) {
186
+ return (this.fillsScreen ? visibleRect : this.contentBBox) ?? this.contentBBox;
187
+ }
161
188
  render(canvas, visibleRect) {
162
189
  if (this.backgroundType === BackgroundType.None) {
163
190
  return;
164
191
  }
165
192
  const clip = this.backgroundType === BackgroundType.Grid;
166
- canvas.startObject(this.contentBBox, clip);
193
+ const contentBBox = this.getFullBoundingBox(visibleRect);
194
+ canvas.startObject(contentBBox, clip);
167
195
  if (this.backgroundType === BackgroundType.SolidColor || this.backgroundType === BackgroundType.Grid) {
168
196
  // If the rectangle for this region contains the visible rect,
169
197
  // we can fill the entire visible rectangle (which may be more efficient than
170
198
  // filling the entire region for this.)
171
199
  if (visibleRect) {
172
- const intersection = visibleRect.intersection(this.contentBBox);
200
+ const intersection = visibleRect.intersection(contentBBox);
173
201
  if (intersection) {
174
202
  canvas.fillRect(intersection, this.mainColor);
175
203
  }
176
204
  }
177
205
  else {
178
- canvas.fillRect(this.contentBBox, this.mainColor);
206
+ canvas.fillRect(contentBBox, this.mainColor);
179
207
  }
180
208
  }
181
209
  if (this.backgroundType === BackgroundType.Grid) {
@@ -213,6 +241,9 @@ export default class BackgroundComponent extends AbstractComponent {
213
241
  isBackground() {
214
242
  return true;
215
243
  }
244
+ getSizingMode() {
245
+ return this.fillsScreen ? ComponentSizingMode.FillScreen : ComponentSizingMode.BoundingBox;
246
+ }
216
247
  serializeToJSON() {
217
248
  return {
218
249
  mainColor: this.mainColor.toHexString(),
@@ -1,6 +1,6 @@
1
1
  import { LineSegment2, Mat33, Rect2 } from '@js-draw/math';
2
2
  import AbstractRenderer from '../rendering/renderers/AbstractRenderer';
3
- import AbstractComponent from './AbstractComponent';
3
+ import AbstractComponent, { ComponentSizingMode } from './AbstractComponent';
4
4
  import { ImageComponentLocalization } from './localization';
5
5
  type GlobalAttrsList = Array<[string, string | null]>;
6
6
  export default class SVGGlobalAttributesObject extends AbstractComponent {
@@ -11,6 +11,7 @@ export default class SVGGlobalAttributesObject extends AbstractComponent {
11
11
  intersects(_lineSegment: LineSegment2): boolean;
12
12
  protected applyTransformation(_affineTransfm: Mat33): void;
13
13
  isSelectable(): boolean;
14
+ getSizingMode(): ComponentSizingMode;
14
15
  protected createClone(): SVGGlobalAttributesObject;
15
16
  description(localization: ImageComponentLocalization): string;
16
17
  protected serializeToJSON(): string | null;
@@ -6,7 +6,7 @@
6
6
  //
7
7
  import { Rect2 } from '@js-draw/math';
8
8
  import SVGRenderer from '../rendering/renderers/SVGRenderer.mjs';
9
- import AbstractComponent from './AbstractComponent.mjs';
9
+ import AbstractComponent, { ComponentSizingMode } from './AbstractComponent.mjs';
10
10
  const componentKind = 'svg-global-attributes';
11
11
  // Stores global SVG attributes (e.g. namespace identifiers.)
12
12
  export default class SVGGlobalAttributesObject extends AbstractComponent {
@@ -32,6 +32,12 @@ export default class SVGGlobalAttributesObject extends AbstractComponent {
32
32
  isSelectable() {
33
33
  return false;
34
34
  }
35
+ getSizingMode() {
36
+ // This component can be shown anywhere (it won't be
37
+ // visible to the user, it just needs to be saved with
38
+ // the image).
39
+ return ComponentSizingMode.Anywhere;
40
+ }
35
41
  createClone() {
36
42
  return new SVGGlobalAttributesObject(this.attrs);
37
43
  }
@@ -48,6 +48,7 @@ export default class Stroke extends AbstractComponent implements RestyleableComp
48
48
  updateStyle(style: ComponentStyle): SerializableCommand;
49
49
  forceStyle(style: ComponentStyle, editor: Editor | null): void;
50
50
  intersects(line: LineSegment2): boolean;
51
+ intersectsRect(rect: Rect2): boolean;
51
52
  render(canvas: AbstractRenderer, visibleRect?: Rect2): void;
52
53
  getProportionalRenderingTime(): number;
53
54
  private bboxForPart;