@myrmidon/gve-snapshot-rendition 2.0.2 → 2.0.3

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/index.js CHANGED
@@ -25917,7 +25917,7 @@ class GveSnapshotRendition extends HTMLElement {
25917
25917
  * of the web component is loaded.
25918
25918
  */
25919
25919
  static get version() {
25920
- return "2.0.2";
25920
+ return "2.0.3";
25921
25921
  }
25922
25922
  constructor() {
25923
25923
  super();
@@ -25929,6 +25929,7 @@ class GveSnapshotRendition extends HTMLElement {
25929
25929
  this._currentVersionIndex = 0;
25930
25930
  this._autoForwardEnabled = false;
25931
25931
  this._autoForwardTimerId = null;
25932
+ this._renderScheduled = false;
25932
25933
  // Initialize with default settings
25933
25934
  this._settings = { ...DEFAULT_SETTINGS };
25934
25935
  this._autoForwardEnabled = this._settings.autoForwardOnGroup;
@@ -26001,14 +26002,20 @@ class GveSnapshotRendition extends HTMLElement {
26001
26002
  this._settings = { ...DEFAULT_SETTINGS, ...value };
26002
26003
  this._logger.setEnabled(this._settings.debug || false);
26003
26004
  this._logger.info("Settings updated", this._settings);
26004
- // When settings change, update renderers
26005
26005
  if (this._textRenderer) {
26006
26006
  this._textRenderer.updateSettings(this._settings);
26007
26007
  }
26008
26008
  if (this._hintRenderer) {
26009
26009
  this._hintRenderer.updateSettings(this._settings);
26010
26010
  }
26011
- this.render();
26011
+ // Build the UI shell on first call; after that just schedule a content
26012
+ // re-render so rapid settings+data assignments coalesce into one render.
26013
+ if (!this._container) {
26014
+ this.render();
26015
+ }
26016
+ else {
26017
+ this.scheduleRenderContent();
26018
+ }
26012
26019
  }
26013
26020
  /**
26014
26021
  * Data property (bindable).
@@ -26110,20 +26117,40 @@ class GveSnapshotRendition extends HTMLElement {
26110
26117
  });
26111
26118
  }
26112
26119
  }
26113
- // Trigger content rendering if renderers are ready
26120
+ // Schedule content rendering. Using scheduleRenderContent() rather than
26121
+ // a direct call ensures that when settings and data are both set in the
26122
+ // same synchronous turn (the common Angular pattern), only one render
26123
+ // fires after both assignments have completed.
26114
26124
  if (this._textRenderer && this._rootSvg && this._baseNodes.length > 0) {
26115
- this.renderContent();
26125
+ this.scheduleRenderContent();
26116
26126
  }
26117
26127
  }
26118
26128
  /**
26119
26129
  * Render the component UI.
26120
26130
  */
26121
26131
  render() {
26132
+ // Cancel any pending coalesced render — initializeGoldenLayout() will
26133
+ // trigger renderContent() once the new DOM is actually ready.
26134
+ this._renderScheduled = false;
26122
26135
  // Destroy existing GL instance before clearing the DOM
26123
26136
  if (this._goldenLayout) {
26124
26137
  this._goldenLayout.destroy();
26125
26138
  this._goldenLayout = undefined;
26126
26139
  }
26140
+ // Destroy pan/zoom — its internal state is tied to the old SVG element.
26141
+ // initializePanZoom() guards against double-init with a null-check,
26142
+ // so it must be reset here or it will silently skip re-initialization.
26143
+ if (this._panZoomInstance) {
26144
+ this._panZoomInstance.destroy();
26145
+ this._panZoomInstance = undefined;
26146
+ }
26147
+ // Kill in-flight GSAP animations on the old SVG tree.
26148
+ if (this._animationEngine) {
26149
+ this._animationEngine.killAll();
26150
+ }
26151
+ // Reset toolbar — processData() only creates it when this is null,
26152
+ // so leaving a stale reference would prevent toolbar rebuild.
26153
+ this._toolbar = undefined;
26127
26154
  this._shadow.innerHTML = "";
26128
26155
  // Add styles (includes inlined GL CSS for Shadow DOM compatibility)
26129
26156
  const style = document.createElement("style");
@@ -26334,6 +26361,22 @@ class GveSnapshotRendition extends HTMLElement {
26334
26361
  this._logger.error("GL", "Failed to reset layout", error);
26335
26362
  }
26336
26363
  }
26364
+ /**
26365
+ * Coalesce concurrent render requests into a single call.
26366
+ * Multiple same-turn invocations (e.g. settings setter + data setter both
26367
+ * firing in the same Angular expression) collapse into one microtask render,
26368
+ * always running after all synchronous assignments have completed so the
26369
+ * latest data is used.
26370
+ */
26371
+ scheduleRenderContent() {
26372
+ if (this._renderScheduled)
26373
+ return;
26374
+ this._renderScheduled = true;
26375
+ Promise.resolve().then(() => {
26376
+ this._renderScheduled = false;
26377
+ this.renderContent();
26378
+ });
26379
+ }
26337
26380
  /**
26338
26381
  * Render content (base text only - starts at v0).
26339
26382
  * Use goToVersionIndex() to navigate to specific versions.
@@ -40804,7 +40847,7 @@ function requireD () {
40804
40847
  + 'pragma private protected public pure ref return scope shared static struct '
40805
40848
  + 'super switch synchronized template this throw try typedef typeid typeof union '
40806
40849
  + 'unittest version void volatile while with __FILE__ __LINE__ __gshared|10 '
40807
- + '__thread __traits __DATE__ __EOF__ __TIME__ __TIMESTAMP__ __VENDOR__ 2.0.2',
40850
+ + '__thread __traits __DATE__ __EOF__ __TIME__ __TIMESTAMP__ __VENDOR__ 2.0.3',
40808
40851
  built_in:
40809
40852
  'bool cdouble cent cfloat char creal dchar delegate double dstring float function '
40810
40853
  + 'idouble ifloat ireal long real short string ubyte ucent uint ulong ushort wchar '
@@ -90587,9 +90630,11 @@ class GveHintDesigner extends HTMLElement {
90587
90630
  const loadDataBtn = this.createButton("load-data", "upload", "Load data from file");
90588
90631
  const clearDataBtn = this.createButton("clear-data", "x-circle", "Clear all data");
90589
90632
  clearDataBtn.classList.add("btn-danger");
90633
+ const exportInkscapeBtn = this.createButton("export-inkscape", "external-link", "Export hint to InkScape SVG");
90590
90634
  dataGroup.appendChild(saveDataBtn);
90591
90635
  dataGroup.appendChild(loadDataBtn);
90592
90636
  dataGroup.appendChild(clearDataBtn);
90637
+ dataGroup.appendChild(exportInkscapeBtn);
90593
90638
  toolbar.appendChild(dataGroup);
90594
90639
  return toolbar;
90595
90640
  }
@@ -91037,6 +91082,7 @@ class GveHintDesigner extends HTMLElement {
91037
91082
  this.addClickListener("save-data", () => this.saveData());
91038
91083
  this.addClickListener("load-data", () => this.loadData());
91039
91084
  this.addClickListener("clear-data", () => this.clearData());
91085
+ this.addClickListener("export-inkscape", () => this.exportToInkscape());
91040
91086
  // Timeline player
91041
91087
  this.addClickListener("play", () => this.playAnimation());
91042
91088
  this.addClickListener("pause", () => this.pauseAnimation());
@@ -92311,6 +92357,76 @@ class GveHintDesigner extends HTMLElement {
92311
92357
  this.showMessage("All data cleared", "success");
92312
92358
  this._logger.info("Data cleared");
92313
92359
  }
92360
+ /**
92361
+ * Export the current hint as an InkScape-compatible SVG file.
92362
+ */
92363
+ exportToInkscape() {
92364
+ if (!this._hintId) {
92365
+ this.showMessage("No hint selected!", "error");
92366
+ return;
92367
+ }
92368
+ const hint = this._data.hints[this._hintId];
92369
+ if (!hint?.svg) {
92370
+ this.showMessage("No SVG to export!", "error");
92371
+ return;
92372
+ }
92373
+ const w = this._settings.hintDesignWidth;
92374
+ const h = this._settings.hintDesignHeight;
92375
+ const hw = w / 2;
92376
+ const hh = h / 2;
92377
+ const resolvedSvg = this.resolveVariables(hint.svg);
92378
+ const unresolvedMatches = resolvedSvg.match(/\{\{[^}]+\}\}/g);
92379
+ const svgContent = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n` +
92380
+ `<svg\n` +
92381
+ ` width="${w}"\n` +
92382
+ ` height="${h}"\n` +
92383
+ ` viewBox="0 0 ${w} ${h}"\n` +
92384
+ ` version="1.1"\n` +
92385
+ ` id="svg1"\n` +
92386
+ ` sodipodi:docname="${this._hintId}.svg"\n` +
92387
+ ` xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"\n` +
92388
+ ` xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"\n` +
92389
+ ` xmlns="http://www.w3.org/2000/svg"\n` +
92390
+ ` xmlns:svg="http://www.w3.org/2000/svg">\n` +
92391
+ ` <sodipodi:namedview\n` +
92392
+ ` id="namedview1"\n` +
92393
+ ` pagecolor="#ffffff"\n` +
92394
+ ` bordercolor="#000000"\n` +
92395
+ ` borderopacity="0.25"\n` +
92396
+ ` inkscape:showpageshadow="2"\n` +
92397
+ ` inkscape:pageopacity="0.0"\n` +
92398
+ ` inkscape:pagecheckerboard="false"\n` +
92399
+ ` inkscape:deskcolor="#d1d1d1"\n` +
92400
+ ` inkscape:document-units="px"\n` +
92401
+ ` inkscape:zoom="3"\n` +
92402
+ ` inkscape:cx="${hw}"\n` +
92403
+ ` inkscape:cy="${hh}"\n` +
92404
+ ` inkscape:window-maximized="0"\n` +
92405
+ ` inkscape:current-layer="g1"\n` +
92406
+ ` showgrid="false" />\n` +
92407
+ ` <defs id="defs1" />\n` +
92408
+ ` <g\n` +
92409
+ ` inkscape:label="Layer 1"\n` +
92410
+ ` inkscape:groupmode="layer"\n` +
92411
+ ` id="layer1" />\n` +
92412
+ ` ${resolvedSvg}\n` +
92413
+ `</svg>`;
92414
+ const blob = new Blob([svgContent], { type: "image/svg+xml" });
92415
+ const url = URL.createObjectURL(blob);
92416
+ const link = document.createElement("a");
92417
+ link.href = url;
92418
+ link.download = `${this._hintId}.svg`;
92419
+ link.click();
92420
+ URL.revokeObjectURL(url);
92421
+ if (unresolvedMatches) {
92422
+ const names = [...new Set(unresolvedMatches)].join(", ");
92423
+ this.showMessage(`Exported "${this._hintId}.svg" — warning: unresolved variables: ${names}`, "info");
92424
+ }
92425
+ else {
92426
+ this.showMessage(`Exported "${this._hintId}.svg" for InkScape`, "success");
92427
+ }
92428
+ this._logger.info("Exported hint to InkScape", this._hintId);
92429
+ }
92314
92430
  /**
92315
92431
  * Load animation code from catalog.
92316
92432
  */