kritzel-stencil 0.1.92 → 0.1.94
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/cjs/index.cjs.js +1 -1
- package/dist/cjs/kritzel-active-users_42.cjs.entry.js +93 -11
- package/dist/cjs/{workspace.migrations-PaftqSjk.js → workspace.migrations-BENHTbRC.js} +101 -9
- package/dist/collection/classes/core/core.class.js +15 -3
- package/dist/collection/classes/objects/base-object.class.js +14 -0
- package/dist/collection/classes/objects/image.class.js +86 -9
- package/dist/collection/classes/registries/icon-registry.class.js +1 -0
- package/dist/collection/classes/structures/object-map.structure.js +8 -0
- package/dist/collection/components/core/kritzel-engine/kritzel-engine.js +68 -6
- package/dist/collection/constants/version.js +1 -1
- package/dist/components/index.js +1 -1
- package/dist/components/kritzel-awareness-cursors.js +1 -1
- package/dist/components/kritzel-back-to-content.js +1 -1
- package/dist/components/kritzel-brush-style.js +1 -1
- package/dist/components/kritzel-context-menu.js +1 -1
- package/dist/components/kritzel-controls.js +1 -1
- package/dist/components/kritzel-editor.js +1 -1
- package/dist/components/kritzel-engine.js +1 -1
- package/dist/components/kritzel-export.js +1 -1
- package/dist/components/kritzel-icon.js +1 -1
- package/dist/components/kritzel-login-dialog.js +1 -1
- package/dist/components/kritzel-master-detail.js +1 -1
- package/dist/components/kritzel-menu-item.js +1 -1
- package/dist/components/kritzel-menu.js +1 -1
- package/dist/components/kritzel-more-menu.js +1 -1
- package/dist/components/kritzel-pill-tabs.js +1 -1
- package/dist/components/kritzel-settings.js +1 -1
- package/dist/components/kritzel-share-dialog.js +1 -1
- package/dist/components/kritzel-split-button.js +1 -1
- package/dist/components/kritzel-tool-config.js +1 -1
- package/dist/components/kritzel-utility-panel.js +1 -1
- package/dist/components/kritzel-workspace-manager.js +1 -1
- package/dist/components/p-A7Ult9iv.js +1 -0
- package/dist/components/p-B2kHVHa_.js +1 -0
- package/dist/components/{p-CRdrQOlL.js → p-BFQVg_eQ.js} +1 -1
- package/dist/components/{p-CrCtvLMx.js → p-BoRQF_Zc.js} +1 -1
- package/dist/components/{p-DRbR0Li3.js → p-BvgGpgKP.js} +1 -1
- package/dist/components/{p-DmNh83AY.js → p-Bx8daVwR.js} +2 -2
- package/dist/components/{p-BMPgR5Bt.js → p-CFzvz-B2.js} +1 -1
- package/dist/components/{p-CKdGsPx9.js → p-CK29qhZR.js} +1 -1
- package/dist/components/{p-D8fQwcNC.js → p-CU6kJPth.js} +1 -1
- package/dist/components/{p-CdvApfJt.js → p-CY9ooSqo.js} +1 -1
- package/dist/components/{p-7yTPTHbQ.js → p-ChQNi67Z.js} +1 -1
- package/dist/components/{p-DXdAYm-y.js → p-ChqeIKg_.js} +1 -1
- package/dist/components/{p-DPmAV68B.js → p-CoyqJSjT.js} +1 -1
- package/dist/components/{p-CowdEK08.js → p-CqYIRmoh.js} +1 -1
- package/dist/components/{p-cVQef3Hq.js → p-Cra28iyu.js} +1 -1
- package/dist/components/{p-CBq-KE9C.js → p-Czaea0WP.js} +1 -1
- package/dist/components/{p-TIoiUjzO.js → p-DACQ8HHJ.js} +1 -1
- package/dist/components/{p-DMvIGnOt.js → p-DVEfOb8T.js} +1 -1
- package/dist/components/{p-DBZyCAsW.js → p-DkT0CXfN.js} +1 -1
- package/dist/components/{p-BTguWTDZ.js → p-mYhFNPgz.js} +1 -1
- package/dist/esm/index.js +2 -2
- package/dist/esm/kritzel-active-users_42.entry.js +93 -11
- package/dist/esm/{workspace.migrations-Cz5xML0x.js → workspace.migrations-CfJnWHNg.js} +101 -9
- package/dist/stencil/index.esm.js +1 -1
- package/dist/stencil/{p-5498d2e1.entry.js → p-5fdd1dea.entry.js} +2 -2
- package/dist/stencil/p-CfJnWHNg.js +1 -0
- package/dist/stencil/stencil.esm.js +1 -1
- package/dist/types/classes/core/core.class.d.ts +13 -2
- package/dist/types/classes/objects/base-object.class.d.ts +12 -0
- package/dist/types/classes/objects/image.class.d.ts +40 -0
- package/dist/types/components/core/kritzel-engine/kritzel-engine.d.ts +9 -1
- package/dist/types/constants/version.d.ts +1 -1
- package/package.json +1 -1
- package/dist/components/p-DQK_4lkI.js +0 -1
- package/dist/components/p-Gm5hSQ-e.js +0 -1
- package/dist/stencil/p-Cz5xML0x.js +0 -1
package/dist/cjs/index.cjs.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var index = require('./index-CFnj_FXt.js');
|
|
4
|
-
var workspace_migrations = require('./workspace.migrations-
|
|
4
|
+
var workspace_migrations = require('./workspace.migrations-BENHTbRC.js');
|
|
5
5
|
var Y = require('yjs');
|
|
6
6
|
require('y-indexeddb');
|
|
7
7
|
require('y-websocket');
|
|
@@ -21767,6 +21767,10 @@ class KritzelObjectMap {
|
|
|
21767
21767
|
objectsToUpdate.forEach(object => {
|
|
21768
21768
|
const existed = this._idMap.has(object.id);
|
|
21769
21769
|
if (existed) {
|
|
21770
|
+
const previous = this._idMap.get(object.id);
|
|
21771
|
+
if (previous && typeof object.adoptTransientStateFrom === 'function') {
|
|
21772
|
+
object.adoptTransientStateFrom(previous);
|
|
21773
|
+
}
|
|
21770
21774
|
this.quadtree.update(object);
|
|
21771
21775
|
this._idMap.set(object.id, object);
|
|
21772
21776
|
updatedObjects.push(object);
|
|
@@ -21782,6 +21786,10 @@ class KritzelObjectMap {
|
|
|
21782
21786
|
selectionGroupsToUpdate.forEach(object => {
|
|
21783
21787
|
const existed = this._idMap.has(object.id);
|
|
21784
21788
|
if (existed) {
|
|
21789
|
+
const previous = this._idMap.get(object.id);
|
|
21790
|
+
if (previous && typeof object.adoptTransientStateFrom === 'function') {
|
|
21791
|
+
object.adoptTransientStateFrom(previous);
|
|
21792
|
+
}
|
|
21785
21793
|
this.quadtree.update(object);
|
|
21786
21794
|
this._idMap.set(object.id, object);
|
|
21787
21795
|
}
|
|
@@ -23324,12 +23332,24 @@ class KritzelCore {
|
|
|
23324
23332
|
}
|
|
23325
23333
|
/**
|
|
23326
23334
|
* Initializes the Yjs document for collaborative editing.
|
|
23327
|
-
* Sets up the app state map with the current sync configuration
|
|
23328
|
-
*
|
|
23335
|
+
* Sets up the app state map with the current sync configuration.
|
|
23336
|
+
*
|
|
23337
|
+
* Asset storage is initialized separately via {@link initializeAssetStorage}
|
|
23338
|
+
* so that it can be deferred until the `assetStorageConfig` prop arrives,
|
|
23339
|
+
* mirroring the lazy-init pattern used for sync config.
|
|
23329
23340
|
*/
|
|
23330
23341
|
async initializeYjs() {
|
|
23331
|
-
await this._assetResolver.init(this._assetStorageConfig);
|
|
23332
23342
|
await this._appStateMap.initialize(this, this._syncConfig);
|
|
23343
|
+
}
|
|
23344
|
+
/**
|
|
23345
|
+
* Initializes the asset storage layer with the current asset storage
|
|
23346
|
+
* configuration. Safe to call multiple times: subsequent calls are
|
|
23347
|
+
* no-ops because {@link KritzelAssetResolver.init} is idempotent.
|
|
23348
|
+
* To apply a new configuration after initialization, destroy the
|
|
23349
|
+
* resolver first or use {@link KritzelEngine.reinitSync}.
|
|
23350
|
+
*/
|
|
23351
|
+
async initializeAssetStorage() {
|
|
23352
|
+
await this._assetResolver.init(this._assetStorageConfig);
|
|
23333
23353
|
this.warnIfAssetStorageMismatched();
|
|
23334
23354
|
}
|
|
23335
23355
|
/**
|
|
@@ -25731,11 +25751,18 @@ const KritzelEngine = class {
|
|
|
25731
25751
|
* such as `HttpAssetProvider` or `PresignedAssetProvider`.
|
|
25732
25752
|
*/
|
|
25733
25753
|
assetStorageConfig;
|
|
25734
|
-
onAssetStorageConfigChange(newValue) {
|
|
25735
|
-
|
|
25736
|
-
//
|
|
25737
|
-
// providers at runtime requires reinitSync().
|
|
25754
|
+
async onAssetStorageConfigChange(newValue) {
|
|
25755
|
+
this._assetStorageConfigRevision++;
|
|
25756
|
+
// Keep core config in sync immediately so late prop updates are not lost.
|
|
25738
25757
|
this.core.setAssetStorageConfig(newValue);
|
|
25758
|
+
// If assetStorageConfig arrives after componentDidLoad, initialize the
|
|
25759
|
+
// asset storage layer now. Mirrors the syncConfig watcher: the resolver
|
|
25760
|
+
// is only initialized once, when a non-undefined config first becomes
|
|
25761
|
+
// available. Subsequent changes after initialization require
|
|
25762
|
+
// `reinitSync()`, identical to syncConfig semantics.
|
|
25763
|
+
if (newValue && !this._isAssetStorageInitialized && this._isViewportReady) {
|
|
25764
|
+
await this.initializeAssetStorage();
|
|
25765
|
+
}
|
|
25739
25766
|
}
|
|
25740
25767
|
/** The current user for awareness broadcasting (name, id, cursor position). */
|
|
25741
25768
|
user;
|
|
@@ -26849,7 +26876,9 @@ const KritzelEngine = class {
|
|
|
26849
26876
|
this.core.store.objects?.clearCursorPosition();
|
|
26850
26877
|
this.core.store.objects?.destroy();
|
|
26851
26878
|
this.core.appStateMap.destroy();
|
|
26879
|
+
this.core.assetResolver.destroy();
|
|
26852
26880
|
this._isYjsInitialized = false;
|
|
26881
|
+
this._isAssetStorageInitialized = false;
|
|
26853
26882
|
await this.initializeSyncAndWorkspace();
|
|
26854
26883
|
}
|
|
26855
26884
|
core;
|
|
@@ -26860,12 +26889,14 @@ const KritzelEngine = class {
|
|
|
26860
26889
|
_lastHadSelectionGroup = false;
|
|
26861
26890
|
_isViewportReady = false;
|
|
26862
26891
|
_isYjsInitialized = false;
|
|
26892
|
+
_isAssetStorageInitialized = false;
|
|
26863
26893
|
_isResolvingActiveWorkspaceId = false;
|
|
26864
26894
|
_stateChangeListenersRegistered = false;
|
|
26865
26895
|
_workspaceInitializationPromise = null;
|
|
26866
26896
|
_workspaceInitializationTargetKey = null;
|
|
26867
26897
|
_syncInitPromise = null;
|
|
26868
26898
|
_syncConfigRevision = 0;
|
|
26899
|
+
_assetStorageConfigRevision = 0;
|
|
26869
26900
|
_isWorkspaceLoading = false;
|
|
26870
26901
|
_defaultUndoState = {
|
|
26871
26902
|
canUndo: false,
|
|
@@ -26963,6 +26994,27 @@ const KritzelEngine = class {
|
|
|
26963
26994
|
this._syncInitPromise = null;
|
|
26964
26995
|
}
|
|
26965
26996
|
}
|
|
26997
|
+
/**
|
|
26998
|
+
* Initializes the asset storage layer with the latest config. Mirrors
|
|
26999
|
+
* the in-flight replay logic of {@link doInitializeSyncAndWorkspace} for
|
|
27000
|
+
* `assetStorageConfig` prop updates that arrive while init is running.
|
|
27001
|
+
*/
|
|
27002
|
+
async initializeAssetStorage() {
|
|
27003
|
+
if (this._isAssetStorageInitialized) {
|
|
27004
|
+
return;
|
|
27005
|
+
}
|
|
27006
|
+
const revisionAtStart = this._assetStorageConfigRevision;
|
|
27007
|
+
this.core.setAssetStorageConfig(this.assetStorageConfig);
|
|
27008
|
+
await this.core.initializeAssetStorage();
|
|
27009
|
+
// If assetStorageConfig changed mid-flight, replay once with the latest
|
|
27010
|
+
// value so late prop updates are not lost.
|
|
27011
|
+
if (revisionAtStart !== this._assetStorageConfigRevision) {
|
|
27012
|
+
this.core.assetResolver.destroy();
|
|
27013
|
+
this.core.setAssetStorageConfig(this.assetStorageConfig);
|
|
27014
|
+
await this.core.initializeAssetStorage();
|
|
27015
|
+
}
|
|
27016
|
+
this._isAssetStorageInitialized = true;
|
|
27017
|
+
}
|
|
26966
27018
|
async doInitializeSyncAndWorkspace() {
|
|
26967
27019
|
// Capture sync config revision to detect prop updates that happen while
|
|
26968
27020
|
// initialization is in-flight.
|
|
@@ -26983,6 +27035,12 @@ const KritzelEngine = class {
|
|
|
26983
27035
|
}
|
|
26984
27036
|
this._isYjsInitialized = true;
|
|
26985
27037
|
}
|
|
27038
|
+
// Initialize the asset storage layer if a config has been provided.
|
|
27039
|
+
// When the prop arrives later, the @Watch('assetStorageConfig') handler
|
|
27040
|
+
// will trigger initializeAssetStorage() instead.
|
|
27041
|
+
if (!this._isAssetStorageInitialized && this.assetStorageConfig) {
|
|
27042
|
+
await this.initializeAssetStorage();
|
|
27043
|
+
}
|
|
26986
27044
|
if (this.activeWorkspaceId) {
|
|
26987
27045
|
const startupWorkspace = this.core.getWorkspaces().find(ws => ws.id === this.activeWorkspaceId);
|
|
26988
27046
|
if (startupWorkspace) {
|
|
@@ -27187,7 +27245,7 @@ const KritzelEngine = class {
|
|
|
27187
27245
|
opacity: object.markedForRemoval ? '0.5' : object.opacity.toString(),
|
|
27188
27246
|
pointerEvents: object.markedForRemoval ? 'none' : 'auto',
|
|
27189
27247
|
overflow: 'visible',
|
|
27190
|
-
}, viewBox: object?.viewBox }, (object.hasStartArrow || object.hasEndArrow) && (index.h("defs", null, object.hasStartArrow && (index.h("marker", { id: object.startMarkerId, markerWidth: object.getArrowSize('start'), markerHeight: object.getArrowSize('start'), refX: 0, refY: object.getArrowSize('start') / 2, orient: "auto-start-reverse", markerUnits: "userSpaceOnUse" }, index.h("path", { d: object.getArrowPath(object.arrows?.start?.style), fill: object.getArrowFill('start'), transform: `scale(${object.getArrowSize('start') / 10})` }))), object.hasEndArrow && (index.h("marker", { id: object.endMarkerId, markerWidth: object.getArrowSize('end'), markerHeight: object.getArrowSize('end'), refX: 0, refY: object.getArrowSize('end') / 2, orient: "auto", markerUnits: "userSpaceOnUse" }, index.h("path", { d: object.getArrowPath(object.arrows?.end?.style), fill: object.getArrowFill('end'), transform: `scale(${object.getArrowSize('end') / 10})` }))))), index.h("path", { d: this.core.anchorManager.computeClippedLinePath(object), fill: "none", stroke: "transparent", "stroke-width": Math.max(object?.strokeWidth || 0, 10), "stroke-linecap": "round" }), index.h("path", { d: this.core.anchorManager.computeClippedLinePath(object), fill: "none", stroke: workspace_migrations.KritzelColorHelper.resolveThemeColor(object?.stroke, currentTheme), "stroke-width": object?.strokeWidth, "stroke-linecap": "round", "marker-start": object.hasStartArrow ? `url(#${object.startMarkerId})` : undefined, "marker-end": object.hasEndArrow ? `url(#${object.endMarkerId})` : undefined }))), workspace_migrations.KritzelClassHelper.isInstanceOf(object, 'KritzelImage') && (index.h("img", { ref: el => el && object.mount(el), src: object.resolvedSrc || object.src, style: {
|
|
27248
|
+
}, viewBox: object?.viewBox }, (object.hasStartArrow || object.hasEndArrow) && (index.h("defs", null, object.hasStartArrow && (index.h("marker", { id: object.startMarkerId, markerWidth: object.getArrowSize('start'), markerHeight: object.getArrowSize('start'), refX: 0, refY: object.getArrowSize('start') / 2, orient: "auto-start-reverse", markerUnits: "userSpaceOnUse" }, index.h("path", { d: object.getArrowPath(object.arrows?.start?.style), fill: object.getArrowFill('start'), transform: `scale(${object.getArrowSize('start') / 10})` }))), object.hasEndArrow && (index.h("marker", { id: object.endMarkerId, markerWidth: object.getArrowSize('end'), markerHeight: object.getArrowSize('end'), refX: 0, refY: object.getArrowSize('end') / 2, orient: "auto", markerUnits: "userSpaceOnUse" }, index.h("path", { d: object.getArrowPath(object.arrows?.end?.style), fill: object.getArrowFill('end'), transform: `scale(${object.getArrowSize('end') / 10})` }))))), index.h("path", { d: this.core.anchorManager.computeClippedLinePath(object), fill: "none", stroke: "transparent", "stroke-width": Math.max(object?.strokeWidth || 0, 10), "stroke-linecap": "round" }), index.h("path", { d: this.core.anchorManager.computeClippedLinePath(object), fill: "none", stroke: workspace_migrations.KritzelColorHelper.resolveThemeColor(object?.stroke, currentTheme), "stroke-width": object?.strokeWidth, "stroke-linecap": "round", "marker-start": object.hasStartArrow ? `url(#${object.startMarkerId})` : undefined, "marker-end": object.hasEndArrow ? `url(#${object.endMarkerId})` : undefined }))), workspace_migrations.KritzelClassHelper.isInstanceOf(object, 'KritzelImage') && object.loadState === 'ready' && (index.h("img", { ref: el => el && object.mount(el), src: object.resolvedSrc || object.src, style: {
|
|
27191
27249
|
position: 'absolute',
|
|
27192
27250
|
left: '0',
|
|
27193
27251
|
top: '0',
|
|
@@ -27205,7 +27263,31 @@ const KritzelEngine = class {
|
|
|
27205
27263
|
overflow: 'visible',
|
|
27206
27264
|
userSelect: 'none',
|
|
27207
27265
|
imageRendering: this.core.store.state.isScaling || this.core.store.state.isPanning ? 'pixelated' : 'auto',
|
|
27208
|
-
}, draggable: false, onDragStart: e => e.preventDefault() })), workspace_migrations.KritzelClassHelper.isInstanceOf(object, '
|
|
27266
|
+
}, draggable: false, onDragStart: e => e.preventDefault() })), workspace_migrations.KritzelClassHelper.isInstanceOf(object, 'KritzelImage') && object.loadState !== 'ready' && (index.h("div", { ref: () => object.ensureLoaded(), style: {
|
|
27267
|
+
position: 'absolute',
|
|
27268
|
+
left: '0',
|
|
27269
|
+
top: '0',
|
|
27270
|
+
width: object.totalWidth + 'px',
|
|
27271
|
+
height: object.totalHeight + 'px',
|
|
27272
|
+
transform: object.rotationDegrees !== 0 ? `rotate(${object.rotationDegrees}deg)` : undefined,
|
|
27273
|
+
transformOrigin: object.rotationDegrees !== 0 ? `${object.totalWidth / 2}px ${object.totalHeight / 2}px` : undefined,
|
|
27274
|
+
opacity: object.markedForRemoval ? '0.5' : object.opacity.toString(),
|
|
27275
|
+
pointerEvents: object.markedForRemoval ? 'none' : 'auto',
|
|
27276
|
+
backgroundColor: workspace_migrations.KritzelColorHelper.resolveThemeColor({ light: '#e5e7eb', dark: '#2a2a2a' }, currentTheme),
|
|
27277
|
+
borderColor: object.loadState === 'error'
|
|
27278
|
+
? workspace_migrations.KritzelColorHelper.resolveThemeColor({ light: '#9ca3af', dark: '#6b7280' }, currentTheme)
|
|
27279
|
+
: workspace_migrations.KritzelColorHelper.resolveThemeColor(object.borderColor, currentTheme),
|
|
27280
|
+
borderWidth: object.loadState === 'error' ? '1px' : object.borderWidth + 'px',
|
|
27281
|
+
borderStyle: 'solid',
|
|
27282
|
+
padding: object.padding + 'px',
|
|
27283
|
+
overflow: 'hidden',
|
|
27284
|
+
userSelect: 'none',
|
|
27285
|
+
display: 'flex',
|
|
27286
|
+
alignItems: 'center',
|
|
27287
|
+
justifyContent: 'center',
|
|
27288
|
+
} }, index.h("kritzel-icon", { name: object.loadState === 'error' ? 'image-off' : 'image', size: Math.max(16, Math.min(object.totalWidth, object.totalHeight) * 0.3), style: {
|
|
27289
|
+
color: workspace_migrations.KritzelColorHelper.resolveThemeColor({ light: '#9ca3af', dark: '#6b7280' }, currentTheme),
|
|
27290
|
+
} }))), workspace_migrations.KritzelClassHelper.isInstanceOf(object, 'KritzelCustomElement') && (index.h("div", { ref: el => el && object.mount(el), style: {
|
|
27209
27291
|
position: 'absolute',
|
|
27210
27292
|
left: '0',
|
|
27211
27293
|
top: '0',
|
|
@@ -28829,7 +28911,7 @@ const KritzelPortal = class {
|
|
|
28829
28911
|
* This file is auto-generated by the version bump scripts.
|
|
28830
28912
|
* Do not modify manually.
|
|
28831
28913
|
*/
|
|
28832
|
-
const KRITZEL_VERSION = '0.1.
|
|
28914
|
+
const KRITZEL_VERSION = '0.1.94';
|
|
28833
28915
|
|
|
28834
28916
|
const kritzelSettingsCss = () => `:host{display:contents}kritzel-dialog{--kritzel-dialog-body-padding:0;--kritzel-dialog-width-large:800px;--kritzel-dialog-height-large:500px}.footer-button{padding:8px 16px;border-radius:6px;cursor:pointer;font-size:14px}.cancel-button{border:1px solid #ebebeb;background:#fff;color:inherit}.cancel-button:hover{background:#f5f5f5}.settings-content{padding:0}.settings-content h3{margin:0 0 16px 0;font-size:18px;font-weight:600;color:var(--kritzel-settings-content-heading-color, #333333)}.settings-content p{margin:0;font-size:14px;color:var(--kritzel-settings-content-text-color, #666666);line-height:1.5}.settings-group{display:flex;flex-direction:column;gap:24px}.settings-item{display:flex;flex-direction:column;gap:8px}.settings-row{display:flex;align-items:center;justify-content:space-between;gap:16px}.settings-label{font-size:14px;font-weight:600;color:var(--kritzel-settings-label-color, #333333);margin:0 0 4px 0}.settings-description{font-size:12px;color:var(--kritzel-settings-description-color, #888888);margin:0;line-height:1.4}.shortcuts-list{display:flex;flex-direction:column;gap:24px}.shortcuts-category{display:flex;flex-direction:column;gap:8px}.shortcuts-category-title{font-size:14px;font-weight:600;color:var(--kritzel-settings-label-color, #333333);margin:0 0 4px 0}.shortcuts-group{display:flex;flex-direction:column;gap:4px}.shortcut-item{display:flex;justify-content:space-between;align-items:center;padding:6px 8px;border-radius:4px;background:var(--kritzel-settings-shortcut-item-bg, rgba(0, 0, 0, 0.02))}.shortcut-label{font-size:14px;color:var(--kritzel-settings-content-text-color, #666666)}.shortcut-key{font-family:monospace;font-size:12px;padding:2px 8px;border-radius:4px;background:var(--kritzel-settings-shortcut-key-bg, #f0f0f0);color:var(--kritzel-settings-shortcut-key-color, #333333);border:1px solid var(--kritzel-settings-shortcut-key-border, #ddd)}`;
|
|
28835
28917
|
|
|
@@ -608,6 +608,20 @@ class KritzelBaseObject {
|
|
|
608
608
|
Object.assign(this, object);
|
|
609
609
|
return this;
|
|
610
610
|
}
|
|
611
|
+
/**
|
|
612
|
+
* Copies transient (non-persisted) state from a previous local instance
|
|
613
|
+
* onto this freshly-revived instance. Called when a remote Yjs update
|
|
614
|
+
* replaces an existing object so that local-only fields (e.g. asset
|
|
615
|
+
* load state, resolved blob URLs) survive across remote position
|
|
616
|
+
* updates and don't visually flicker.
|
|
617
|
+
*
|
|
618
|
+
* Default implementation is a no-op; subclasses with transient fields
|
|
619
|
+
* should override.
|
|
620
|
+
* @param previous - The previous local instance being replaced.
|
|
621
|
+
*/
|
|
622
|
+
adoptTransientStateFrom(_previous) {
|
|
623
|
+
// no-op by default
|
|
624
|
+
}
|
|
611
625
|
/**
|
|
612
626
|
* Type guard to check if this object is of a specific class type.
|
|
613
627
|
* Compares against the __class__ property.
|
|
@@ -16241,6 +16255,20 @@ class KritzelImage extends KritzelBaseObject {
|
|
|
16241
16255
|
* Not serialized into the Yjs document.
|
|
16242
16256
|
*/
|
|
16243
16257
|
loadState = 'idle';
|
|
16258
|
+
/**
|
|
16259
|
+
* Maximum number of resolution attempts per page load before the
|
|
16260
|
+
* image is considered permanently failed. Defaults to 3.
|
|
16261
|
+
*
|
|
16262
|
+
* The counter is transient (not persisted to Yjs) and resets every
|
|
16263
|
+
* time a fresh `KritzelImage` instance is constructed, so a full page
|
|
16264
|
+
* reload always grants a fresh budget.
|
|
16265
|
+
*/
|
|
16266
|
+
maxLoadAttempts = 3;
|
|
16267
|
+
/**
|
|
16268
|
+
* Number of resolution attempts that have been issued so far for the
|
|
16269
|
+
* current page load. Not serialized into the Yjs document.
|
|
16270
|
+
*/
|
|
16271
|
+
loadAttempts = 0;
|
|
16244
16272
|
/**
|
|
16245
16273
|
* Creates a new KritzelImage instance.
|
|
16246
16274
|
* @param config - Optional partial configuration object to initialize image properties
|
|
@@ -16260,6 +16288,7 @@ class KritzelImage extends KritzelBaseObject {
|
|
|
16260
16288
|
this.height = config?.height || 0;
|
|
16261
16289
|
this.maxWidth = config?.maxWidth ?? 300;
|
|
16262
16290
|
this.maxHeight = config?.maxHeight ?? 300;
|
|
16291
|
+
this.maxLoadAttempts = config?.maxLoadAttempts ?? 3;
|
|
16263
16292
|
}
|
|
16264
16293
|
/**
|
|
16265
16294
|
* Factory method to create a new KritzelImage instance with core integration.
|
|
@@ -16311,21 +16340,56 @@ class KritzelImage extends KritzelBaseObject {
|
|
|
16311
16340
|
// Strip transient, non-persistent rendering state.
|
|
16312
16341
|
delete plain.resolvedSrc;
|
|
16313
16342
|
delete plain.loadState;
|
|
16343
|
+
delete plain.loadAttempts;
|
|
16314
16344
|
return plain;
|
|
16315
16345
|
}
|
|
16346
|
+
/**
|
|
16347
|
+
* Carries transient asset-loading state (resolved URL, load lifecycle,
|
|
16348
|
+
* retry counters) over from the previous local instance when a remote
|
|
16349
|
+
* Yjs update produces a freshly-revived replacement. Without this,
|
|
16350
|
+
* frequent remote updates (e.g. another user dragging the image)
|
|
16351
|
+
* would reset `loadState` to `'idle'` on every tick and cause the
|
|
16352
|
+
* skeleton to flicker between renders.
|
|
16353
|
+
*/
|
|
16354
|
+
adoptTransientStateFrom(previous) {
|
|
16355
|
+
if (!(previous instanceof KritzelImage))
|
|
16356
|
+
return;
|
|
16357
|
+
// Only carry over the resolved URL when the asset reference itself
|
|
16358
|
+
// hasn't changed; otherwise the new asset must be re-resolved.
|
|
16359
|
+
if (previous.assetId === this.assetId) {
|
|
16360
|
+
this.resolvedSrc = previous.resolvedSrc;
|
|
16361
|
+
this.loadState = previous.loadState;
|
|
16362
|
+
this.loadAttempts = previous.loadAttempts;
|
|
16363
|
+
}
|
|
16364
|
+
}
|
|
16316
16365
|
/**
|
|
16317
16366
|
* Triggers (idempotent) resolution of the referenced asset. Updates
|
|
16318
16367
|
* `resolvedSrc` and `loadState` as the resolution progresses and
|
|
16319
16368
|
* schedules a re-render when the URL is available.
|
|
16369
|
+
*
|
|
16370
|
+
* On failure, retries automatically up to {@link maxLoadAttempts}
|
|
16371
|
+
* times per page load with a short exponential backoff. Once the
|
|
16372
|
+
* budget is exhausted, the image is marked permanently `'error'` for
|
|
16373
|
+
* the remainder of the page load to avoid hammering an unavailable
|
|
16374
|
+
* backend; a full reload (which constructs a fresh instance) grants
|
|
16375
|
+
* a new budget.
|
|
16320
16376
|
*/
|
|
16321
16377
|
ensureResolved() {
|
|
16322
16378
|
if (this.loadState === 'loading' || this.loadState === 'ready')
|
|
16323
16379
|
return;
|
|
16380
|
+
if (this.loadState === 'error')
|
|
16381
|
+
return;
|
|
16324
16382
|
if (!this.assetId)
|
|
16325
16383
|
return;
|
|
16326
16384
|
if (!this._core?.assetResolver)
|
|
16327
16385
|
return;
|
|
16386
|
+
if (this.loadAttempts >= this.maxLoadAttempts) {
|
|
16387
|
+
this.loadState = 'error';
|
|
16388
|
+
return;
|
|
16389
|
+
}
|
|
16328
16390
|
this.loadState = 'loading';
|
|
16391
|
+
this.loadAttempts += 1;
|
|
16392
|
+
const attempt = this.loadAttempts;
|
|
16329
16393
|
this._core.assetResolver
|
|
16330
16394
|
.resolve(this.assetId)
|
|
16331
16395
|
.then(url => {
|
|
@@ -16334,9 +16398,24 @@ class KritzelImage extends KritzelBaseObject {
|
|
|
16334
16398
|
this._core?.rerender();
|
|
16335
16399
|
})
|
|
16336
16400
|
.catch(err => {
|
|
16337
|
-
this.
|
|
16338
|
-
console.warn(`[KritzelImage] Failed to resolve asset ${this.assetId}
|
|
16339
|
-
|
|
16401
|
+
const attemptsExhausted = this.loadAttempts >= this.maxLoadAttempts;
|
|
16402
|
+
console.warn(`[KritzelImage] Failed to resolve asset ${this.assetId} ` +
|
|
16403
|
+
`(attempt ${attempt}/${this.maxLoadAttempts}):`, err);
|
|
16404
|
+
if (attemptsExhausted) {
|
|
16405
|
+
this.loadState = 'error';
|
|
16406
|
+
this._core?.rerender();
|
|
16407
|
+
return;
|
|
16408
|
+
}
|
|
16409
|
+
// Schedule a retry with a small exponential backoff. Reset to
|
|
16410
|
+
// 'idle' so the next call (or scheduled tick) is allowed to
|
|
16411
|
+
// proceed past the early-return guard.
|
|
16412
|
+
this.loadState = 'idle';
|
|
16413
|
+
const backoffMs = Math.min(2000, 250 * Math.pow(2, attempt - 1));
|
|
16414
|
+
setTimeout(() => {
|
|
16415
|
+
if (this.loadState === 'idle') {
|
|
16416
|
+
this.ensureResolved();
|
|
16417
|
+
}
|
|
16418
|
+
}, backoffMs);
|
|
16340
16419
|
});
|
|
16341
16420
|
}
|
|
16342
16421
|
/**
|
|
@@ -16370,13 +16449,15 @@ class KritzelImage extends KritzelBaseObject {
|
|
|
16370
16449
|
});
|
|
16371
16450
|
}
|
|
16372
16451
|
/**
|
|
16373
|
-
*
|
|
16374
|
-
* the
|
|
16375
|
-
*
|
|
16376
|
-
*
|
|
16452
|
+
* Kicks off asset resolution (or legacy data-URL migration) without
|
|
16453
|
+
* requiring the `<img>` element to be mounted. Safe to call multiple
|
|
16454
|
+
* times; both underlying paths are idempotent.
|
|
16455
|
+
*
|
|
16456
|
+
* Useful for the renderer's loading-skeleton path, where the actual
|
|
16457
|
+
* `<img>` is not in the DOM yet but the asset bytes still need to
|
|
16458
|
+
* start loading.
|
|
16377
16459
|
*/
|
|
16378
|
-
|
|
16379
|
-
super.mount(element);
|
|
16460
|
+
ensureLoaded() {
|
|
16380
16461
|
if (this.assetId && !this.resolvedSrc) {
|
|
16381
16462
|
this.ensureResolved();
|
|
16382
16463
|
}
|
|
@@ -16384,6 +16465,16 @@ class KritzelImage extends KritzelBaseObject {
|
|
|
16384
16465
|
this.migrateLegacyDataUrlIfNeeded();
|
|
16385
16466
|
}
|
|
16386
16467
|
}
|
|
16468
|
+
/**
|
|
16469
|
+
* Overrides base mount to kick off asset resolution the first time
|
|
16470
|
+
* the image is attached to the DOM. Legacy images persisted with an
|
|
16471
|
+
* inline `src` data URL are opportunistically migrated to the asset
|
|
16472
|
+
* storage layer on first mount.
|
|
16473
|
+
*/
|
|
16474
|
+
mount(element) {
|
|
16475
|
+
super.mount(element);
|
|
16476
|
+
this.ensureLoaded();
|
|
16477
|
+
}
|
|
16387
16478
|
/**
|
|
16388
16479
|
* Creates a KritzelImage from a URL, handling image loading and dimension calculation.
|
|
16389
16480
|
* Loads the image, calculates scaled dimensions respecting maxWidth/maxHeight constraints,
|
|
@@ -20260,6 +20351,7 @@ KritzelIconRegistry.registerIcons({
|
|
|
20260
20351
|
'shape-ellipse': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="12" rx="10" ry="8"/></svg>',
|
|
20261
20352
|
'shape-triangle': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3L22 21H2L12 3Z"/></svg>',
|
|
20262
20353
|
'image': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></svg>',
|
|
20354
|
+
'image-off': '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-image-off-icon lucide-image-off"><line x1="2" x2="22" y1="2" y2="22"/><path d="M10.41 10.41a2 2 0 1 1-2.83-2.83"/><line x1="13.5" x2="6" y1="13.5" y2="21"/><line x1="18" x2="21" y1="12" y2="15"/><path d="M3.59 3.59A1.99 1.99 0 0 0 3 5v14a2 2 0 0 0 2 2h14c.55 0 1.052-.22 1.41-.59"/><path d="M21 15V5a2 2 0 0 0-2-2H9"/></svg>',
|
|
20263
20355
|
'chevron-down': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>',
|
|
20264
20356
|
'chevron-up': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="m18 15-6-6-6 6"/></svg>',
|
|
20265
20357
|
'chevron-left': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-left-icon lucide-chevron-left"><path d="m15 18-6-6 6-6"/></svg>',
|
|
@@ -168,12 +168,24 @@ export class KritzelCore {
|
|
|
168
168
|
}
|
|
169
169
|
/**
|
|
170
170
|
* Initializes the Yjs document for collaborative editing.
|
|
171
|
-
* Sets up the app state map with the current sync configuration
|
|
172
|
-
*
|
|
171
|
+
* Sets up the app state map with the current sync configuration.
|
|
172
|
+
*
|
|
173
|
+
* Asset storage is initialized separately via {@link initializeAssetStorage}
|
|
174
|
+
* so that it can be deferred until the `assetStorageConfig` prop arrives,
|
|
175
|
+
* mirroring the lazy-init pattern used for sync config.
|
|
173
176
|
*/
|
|
174
177
|
async initializeYjs() {
|
|
175
|
-
await this._assetResolver.init(this._assetStorageConfig);
|
|
176
178
|
await this._appStateMap.initialize(this, this._syncConfig);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Initializes the asset storage layer with the current asset storage
|
|
182
|
+
* configuration. Safe to call multiple times: subsequent calls are
|
|
183
|
+
* no-ops because {@link KritzelAssetResolver.init} is idempotent.
|
|
184
|
+
* To apply a new configuration after initialization, destroy the
|
|
185
|
+
* resolver first or use {@link KritzelEngine.reinitSync}.
|
|
186
|
+
*/
|
|
187
|
+
async initializeAssetStorage() {
|
|
188
|
+
await this._assetResolver.init(this._assetStorageConfig);
|
|
177
189
|
this.warnIfAssetStorageMismatched();
|
|
178
190
|
}
|
|
179
191
|
/**
|
|
@@ -371,6 +371,20 @@ export class KritzelBaseObject {
|
|
|
371
371
|
Object.assign(this, object);
|
|
372
372
|
return this;
|
|
373
373
|
}
|
|
374
|
+
/**
|
|
375
|
+
* Copies transient (non-persisted) state from a previous local instance
|
|
376
|
+
* onto this freshly-revived instance. Called when a remote Yjs update
|
|
377
|
+
* replaces an existing object so that local-only fields (e.g. asset
|
|
378
|
+
* load state, resolved blob URLs) survive across remote position
|
|
379
|
+
* updates and don't visually flicker.
|
|
380
|
+
*
|
|
381
|
+
* Default implementation is a no-op; subclasses with transient fields
|
|
382
|
+
* should override.
|
|
383
|
+
* @param previous - The previous local instance being replaced.
|
|
384
|
+
*/
|
|
385
|
+
adoptTransientStateFrom(_previous) {
|
|
386
|
+
// no-op by default
|
|
387
|
+
}
|
|
374
388
|
/**
|
|
375
389
|
* Type guard to check if this object is of a specific class type.
|
|
376
390
|
* Compares against the __class__ property.
|
|
@@ -49,6 +49,20 @@ export class KritzelImage extends KritzelBaseObject {
|
|
|
49
49
|
* Not serialized into the Yjs document.
|
|
50
50
|
*/
|
|
51
51
|
loadState = 'idle';
|
|
52
|
+
/**
|
|
53
|
+
* Maximum number of resolution attempts per page load before the
|
|
54
|
+
* image is considered permanently failed. Defaults to 3.
|
|
55
|
+
*
|
|
56
|
+
* The counter is transient (not persisted to Yjs) and resets every
|
|
57
|
+
* time a fresh `KritzelImage` instance is constructed, so a full page
|
|
58
|
+
* reload always grants a fresh budget.
|
|
59
|
+
*/
|
|
60
|
+
maxLoadAttempts = 3;
|
|
61
|
+
/**
|
|
62
|
+
* Number of resolution attempts that have been issued so far for the
|
|
63
|
+
* current page load. Not serialized into the Yjs document.
|
|
64
|
+
*/
|
|
65
|
+
loadAttempts = 0;
|
|
52
66
|
/**
|
|
53
67
|
* Creates a new KritzelImage instance.
|
|
54
68
|
* @param config - Optional partial configuration object to initialize image properties
|
|
@@ -68,6 +82,7 @@ export class KritzelImage extends KritzelBaseObject {
|
|
|
68
82
|
this.height = config?.height || 0;
|
|
69
83
|
this.maxWidth = config?.maxWidth ?? 300;
|
|
70
84
|
this.maxHeight = config?.maxHeight ?? 300;
|
|
85
|
+
this.maxLoadAttempts = config?.maxLoadAttempts ?? 3;
|
|
71
86
|
}
|
|
72
87
|
/**
|
|
73
88
|
* Factory method to create a new KritzelImage instance with core integration.
|
|
@@ -119,21 +134,56 @@ export class KritzelImage extends KritzelBaseObject {
|
|
|
119
134
|
// Strip transient, non-persistent rendering state.
|
|
120
135
|
delete plain.resolvedSrc;
|
|
121
136
|
delete plain.loadState;
|
|
137
|
+
delete plain.loadAttempts;
|
|
122
138
|
return plain;
|
|
123
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Carries transient asset-loading state (resolved URL, load lifecycle,
|
|
142
|
+
* retry counters) over from the previous local instance when a remote
|
|
143
|
+
* Yjs update produces a freshly-revived replacement. Without this,
|
|
144
|
+
* frequent remote updates (e.g. another user dragging the image)
|
|
145
|
+
* would reset `loadState` to `'idle'` on every tick and cause the
|
|
146
|
+
* skeleton to flicker between renders.
|
|
147
|
+
*/
|
|
148
|
+
adoptTransientStateFrom(previous) {
|
|
149
|
+
if (!(previous instanceof KritzelImage))
|
|
150
|
+
return;
|
|
151
|
+
// Only carry over the resolved URL when the asset reference itself
|
|
152
|
+
// hasn't changed; otherwise the new asset must be re-resolved.
|
|
153
|
+
if (previous.assetId === this.assetId) {
|
|
154
|
+
this.resolvedSrc = previous.resolvedSrc;
|
|
155
|
+
this.loadState = previous.loadState;
|
|
156
|
+
this.loadAttempts = previous.loadAttempts;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
124
159
|
/**
|
|
125
160
|
* Triggers (idempotent) resolution of the referenced asset. Updates
|
|
126
161
|
* `resolvedSrc` and `loadState` as the resolution progresses and
|
|
127
162
|
* schedules a re-render when the URL is available.
|
|
163
|
+
*
|
|
164
|
+
* On failure, retries automatically up to {@link maxLoadAttempts}
|
|
165
|
+
* times per page load with a short exponential backoff. Once the
|
|
166
|
+
* budget is exhausted, the image is marked permanently `'error'` for
|
|
167
|
+
* the remainder of the page load to avoid hammering an unavailable
|
|
168
|
+
* backend; a full reload (which constructs a fresh instance) grants
|
|
169
|
+
* a new budget.
|
|
128
170
|
*/
|
|
129
171
|
ensureResolved() {
|
|
130
172
|
if (this.loadState === 'loading' || this.loadState === 'ready')
|
|
131
173
|
return;
|
|
174
|
+
if (this.loadState === 'error')
|
|
175
|
+
return;
|
|
132
176
|
if (!this.assetId)
|
|
133
177
|
return;
|
|
134
178
|
if (!this._core?.assetResolver)
|
|
135
179
|
return;
|
|
180
|
+
if (this.loadAttempts >= this.maxLoadAttempts) {
|
|
181
|
+
this.loadState = 'error';
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
136
184
|
this.loadState = 'loading';
|
|
185
|
+
this.loadAttempts += 1;
|
|
186
|
+
const attempt = this.loadAttempts;
|
|
137
187
|
this._core.assetResolver
|
|
138
188
|
.resolve(this.assetId)
|
|
139
189
|
.then(url => {
|
|
@@ -142,9 +192,24 @@ export class KritzelImage extends KritzelBaseObject {
|
|
|
142
192
|
this._core?.rerender();
|
|
143
193
|
})
|
|
144
194
|
.catch(err => {
|
|
145
|
-
this.
|
|
146
|
-
console.warn(`[KritzelImage] Failed to resolve asset ${this.assetId}
|
|
147
|
-
|
|
195
|
+
const attemptsExhausted = this.loadAttempts >= this.maxLoadAttempts;
|
|
196
|
+
console.warn(`[KritzelImage] Failed to resolve asset ${this.assetId} ` +
|
|
197
|
+
`(attempt ${attempt}/${this.maxLoadAttempts}):`, err);
|
|
198
|
+
if (attemptsExhausted) {
|
|
199
|
+
this.loadState = 'error';
|
|
200
|
+
this._core?.rerender();
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
// Schedule a retry with a small exponential backoff. Reset to
|
|
204
|
+
// 'idle' so the next call (or scheduled tick) is allowed to
|
|
205
|
+
// proceed past the early-return guard.
|
|
206
|
+
this.loadState = 'idle';
|
|
207
|
+
const backoffMs = Math.min(2000, 250 * Math.pow(2, attempt - 1));
|
|
208
|
+
setTimeout(() => {
|
|
209
|
+
if (this.loadState === 'idle') {
|
|
210
|
+
this.ensureResolved();
|
|
211
|
+
}
|
|
212
|
+
}, backoffMs);
|
|
148
213
|
});
|
|
149
214
|
}
|
|
150
215
|
/**
|
|
@@ -178,13 +243,15 @@ export class KritzelImage extends KritzelBaseObject {
|
|
|
178
243
|
});
|
|
179
244
|
}
|
|
180
245
|
/**
|
|
181
|
-
*
|
|
182
|
-
* the
|
|
183
|
-
*
|
|
184
|
-
*
|
|
246
|
+
* Kicks off asset resolution (or legacy data-URL migration) without
|
|
247
|
+
* requiring the `<img>` element to be mounted. Safe to call multiple
|
|
248
|
+
* times; both underlying paths are idempotent.
|
|
249
|
+
*
|
|
250
|
+
* Useful for the renderer's loading-skeleton path, where the actual
|
|
251
|
+
* `<img>` is not in the DOM yet but the asset bytes still need to
|
|
252
|
+
* start loading.
|
|
185
253
|
*/
|
|
186
|
-
|
|
187
|
-
super.mount(element);
|
|
254
|
+
ensureLoaded() {
|
|
188
255
|
if (this.assetId && !this.resolvedSrc) {
|
|
189
256
|
this.ensureResolved();
|
|
190
257
|
}
|
|
@@ -192,6 +259,16 @@ export class KritzelImage extends KritzelBaseObject {
|
|
|
192
259
|
this.migrateLegacyDataUrlIfNeeded();
|
|
193
260
|
}
|
|
194
261
|
}
|
|
262
|
+
/**
|
|
263
|
+
* Overrides base mount to kick off asset resolution the first time
|
|
264
|
+
* the image is attached to the DOM. Legacy images persisted with an
|
|
265
|
+
* inline `src` data URL are opportunistically migrated to the asset
|
|
266
|
+
* storage layer on first mount.
|
|
267
|
+
*/
|
|
268
|
+
mount(element) {
|
|
269
|
+
super.mount(element);
|
|
270
|
+
this.ensureLoaded();
|
|
271
|
+
}
|
|
195
272
|
/**
|
|
196
273
|
* Creates a KritzelImage from a URL, handling image loading and dimension calculation.
|
|
197
274
|
* Loads the image, calculates scaled dimensions respecting maxWidth/maxHeight constraints,
|
|
@@ -64,6 +64,7 @@ KritzelIconRegistry.registerIcons({
|
|
|
64
64
|
'shape-ellipse': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="12" rx="10" ry="8"/></svg>',
|
|
65
65
|
'shape-triangle': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3L22 21H2L12 3Z"/></svg>',
|
|
66
66
|
'image': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></svg>',
|
|
67
|
+
'image-off': '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-image-off-icon lucide-image-off"><line x1="2" x2="22" y1="2" y2="22"/><path d="M10.41 10.41a2 2 0 1 1-2.83-2.83"/><line x1="13.5" x2="6" y1="13.5" y2="21"/><line x1="18" x2="21" y1="12" y2="15"/><path d="M3.59 3.59A1.99 1.99 0 0 0 3 5v14a2 2 0 0 0 2 2h14c.55 0 1.052-.22 1.41-.59"/><path d="M21 15V5a2 2 0 0 0-2-2H9"/></svg>',
|
|
67
68
|
'chevron-down': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>',
|
|
68
69
|
'chevron-up': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="m18 15-6-6-6 6"/></svg>',
|
|
69
70
|
'chevron-left': '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-left-icon lucide-chevron-left"><path d="m15 18-6-6 6-6"/></svg>',
|
|
@@ -438,6 +438,10 @@ export class KritzelObjectMap {
|
|
|
438
438
|
objectsToUpdate.forEach(object => {
|
|
439
439
|
const existed = this._idMap.has(object.id);
|
|
440
440
|
if (existed) {
|
|
441
|
+
const previous = this._idMap.get(object.id);
|
|
442
|
+
if (previous && typeof object.adoptTransientStateFrom === 'function') {
|
|
443
|
+
object.adoptTransientStateFrom(previous);
|
|
444
|
+
}
|
|
441
445
|
this.quadtree.update(object);
|
|
442
446
|
this._idMap.set(object.id, object);
|
|
443
447
|
updatedObjects.push(object);
|
|
@@ -453,6 +457,10 @@ export class KritzelObjectMap {
|
|
|
453
457
|
selectionGroupsToUpdate.forEach(object => {
|
|
454
458
|
const existed = this._idMap.has(object.id);
|
|
455
459
|
if (existed) {
|
|
460
|
+
const previous = this._idMap.get(object.id);
|
|
461
|
+
if (previous && typeof object.adoptTransientStateFrom === 'function') {
|
|
462
|
+
object.adoptTransientStateFrom(previous);
|
|
463
|
+
}
|
|
456
464
|
this.quadtree.update(object);
|
|
457
465
|
this._idMap.set(object.id, object);
|
|
458
466
|
}
|