@reffy/infinite-canvas 0.0.10 → 0.0.12
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/assets/index-KcoKIUJZ.js +281 -0
- package/dist/index.html +54 -0
- package/esm/Canvas.d.ts.map +1 -1
- package/esm/Canvas.js +104 -113
- package/esm/Canvas.js.map +1 -1
- package/esm/Component.d.ts.map +1 -1
- package/esm/Component.js +274 -304
- package/esm/Component.js.map +1 -1
- package/esm/manager/ContextMenu.js +4 -17
- package/esm/manager/ContextMenu.js.map +1 -1
- package/esm/manager/Selection.js +87 -100
- package/esm/manager/Selection.js.map +1 -1
- package/esm/serializer/serializer.d.ts.map +1 -1
- package/esm/serializer/serializer.js +11 -5
- package/esm/serializer/serializer.js.map +1 -1
- package/esm/shapes/Shape.js +1 -1
- package/esm/shapes/Shape.js.map +1 -1
- package/lib/Canvas.d.ts.map +1 -1
- package/lib/Canvas.js +104 -113
- package/lib/Canvas.js.map +1 -1
- package/lib/Component.d.ts.map +1 -1
- package/lib/Component.js +274 -304
- package/lib/Component.js.map +1 -1
- package/lib/manager/ContextMenu.js +4 -17
- package/lib/manager/ContextMenu.js.map +1 -1
- package/lib/manager/Selection.js +87 -100
- package/lib/manager/Selection.js.map +1 -1
- package/lib/serializer/serializer.d.ts.map +1 -1
- package/lib/serializer/serializer.js +11 -5
- package/lib/serializer/serializer.js.map +1 -1
- package/lib/shapes/Shape.js +1 -1
- package/lib/shapes/Shape.js.map +1 -1
- package/package.json +3 -2
- package/dist/index.js +0 -4110
- package/dist/index.js.map +0 -1
- package/dist/index.umd.js +0 -276
- package/dist/index.umd.js.map +0 -1
package/esm/Component.js
CHANGED
|
@@ -4,18 +4,6 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
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 __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
13
|
-
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
14
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
15
|
-
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");
|
|
16
|
-
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
17
|
-
};
|
|
18
|
-
var _InfiniteCanvasElement_canvas, _InfiniteCanvasElement_eventHub, _InfiniteCanvasElement_resizeObserver, _InfiniteCanvasElement_history, _InfiniteCanvasElement_fileStorage, _InfiniteCanvasElement_canvasStorage, _InfiniteCanvasElement_saveFrequency, _InfiniteCanvasElement_timeoutId, _InfiniteCanvasElement_intervalId, _InfiniteCanvasElement_rootDiv, _InfiniteCanvasElement_onChange, _InfiniteCanvasElement_singleImageMenuOptions, _InfiniteCanvasElement_multiImageMenuOptions, _InfiniteCanvasElement_canvasImageMenuOptions;
|
|
19
7
|
import { CanvasHistory } from "./history";
|
|
20
8
|
import { Canvas } from "./Canvas";
|
|
21
9
|
import { LitElement, css } from "lit";
|
|
@@ -28,7 +16,6 @@ import { addContextMenu, clearContextMenu, createBasicImageMenuOptions, createCa
|
|
|
28
16
|
import { DefaultIndexedDbStorage, DefaultLocalStorage, } from "./storage";
|
|
29
17
|
import EventEmitter from "eventemitter3";
|
|
30
18
|
import { hideLoader, showLoader } from "./loader";
|
|
31
|
-
import Stats from "stats.js";
|
|
32
19
|
let InfiniteCanvasElement = class InfiniteCanvasElement extends LitElement {
|
|
33
20
|
constructor() {
|
|
34
21
|
super(...arguments);
|
|
@@ -36,20 +23,7 @@ let InfiniteCanvasElement = class InfiniteCanvasElement extends LitElement {
|
|
|
36
23
|
this.name = "Reffy";
|
|
37
24
|
/** There are two acceptable options: fullscreen (default) and windowed. Fullscreen mode will ignore the width and height you inputed. */
|
|
38
25
|
this.displayMode = "fullscreen";
|
|
39
|
-
|
|
40
|
-
_InfiniteCanvasElement_eventHub.set(this, void 0);
|
|
41
|
-
_InfiniteCanvasElement_resizeObserver.set(this, void 0);
|
|
42
|
-
_InfiniteCanvasElement_history.set(this, void 0);
|
|
43
|
-
_InfiniteCanvasElement_fileStorage.set(this, void 0);
|
|
44
|
-
_InfiniteCanvasElement_canvasStorage.set(this, void 0);
|
|
45
|
-
_InfiniteCanvasElement_saveFrequency.set(this, 300000);
|
|
46
|
-
_InfiniteCanvasElement_timeoutId.set(this, void 0);
|
|
47
|
-
_InfiniteCanvasElement_intervalId.set(this, void 0);
|
|
48
|
-
_InfiniteCanvasElement_rootDiv.set(this, void 0);
|
|
49
|
-
_InfiniteCanvasElement_onChange.set(this, void 0);
|
|
50
|
-
_InfiniteCanvasElement_singleImageMenuOptions.set(this, void 0);
|
|
51
|
-
_InfiniteCanvasElement_multiImageMenuOptions.set(this, void 0);
|
|
52
|
-
_InfiniteCanvasElement_canvasImageMenuOptions.set(this, void 0);
|
|
26
|
+
this.#saveFrequency = 300000;
|
|
53
27
|
// Global helper
|
|
54
28
|
this.handleGlobalPointerDown = (e) => {
|
|
55
29
|
if (!this.contains(e.target) &&
|
|
@@ -58,29 +32,179 @@ let InfiniteCanvasElement = class InfiniteCanvasElement extends LitElement {
|
|
|
58
32
|
}
|
|
59
33
|
};
|
|
60
34
|
}
|
|
35
|
+
static { this.properties = {
|
|
36
|
+
name: { type: String, reflect: true },
|
|
37
|
+
width: { type: String, reflect: true },
|
|
38
|
+
height: { type: String, reflect: true },
|
|
39
|
+
displayMode: { type: String, reflect: true },
|
|
40
|
+
}; }
|
|
41
|
+
static { this.styles = css `
|
|
42
|
+
:host {
|
|
43
|
+
position: relative;
|
|
44
|
+
overflow: hidden;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.context-menu {
|
|
48
|
+
position: absolute;
|
|
49
|
+
background: white;
|
|
50
|
+
min-width: 180px;
|
|
51
|
+
background: var(--menu-bg, #fff);
|
|
52
|
+
border-radius: 6px;
|
|
53
|
+
border: 1px solid var(--menu-border, #9f9f9fff);
|
|
54
|
+
box-sizing: border-box;
|
|
55
|
+
padding: 6px 0;
|
|
56
|
+
display: flex;
|
|
57
|
+
gap: 2px;
|
|
58
|
+
flex-direction: column;
|
|
59
|
+
font-family: system-ui, sans-serif;
|
|
60
|
+
animation: fadeInMenu 0.13s cubic-bezier(0.4, 0, 0.2, 1);
|
|
61
|
+
overflow: scroll;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@keyframes fadeInMenu {
|
|
65
|
+
from {
|
|
66
|
+
opacity: 0;
|
|
67
|
+
transform: translateY(8px);
|
|
68
|
+
}
|
|
69
|
+
to {
|
|
70
|
+
opacity: 1;
|
|
71
|
+
transform: none;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.context-menu button {
|
|
76
|
+
all: unset;
|
|
77
|
+
display: flex;
|
|
78
|
+
align-items: center;
|
|
79
|
+
box-sizing: border-box;
|
|
80
|
+
width: 100%;
|
|
81
|
+
padding: 8px 18px;
|
|
82
|
+
font-size: 15px;
|
|
83
|
+
color: var(--menu-fg, #222);
|
|
84
|
+
background: none;
|
|
85
|
+
cursor: pointer;
|
|
86
|
+
transition:
|
|
87
|
+
background 0.1s,
|
|
88
|
+
color 0.1s;
|
|
89
|
+
user-select: none;
|
|
90
|
+
outline: none;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.context-menu button:hover,
|
|
94
|
+
.context-menu button:focus-visible {
|
|
95
|
+
background: var(--menu-hover, #c7d5eaff);
|
|
96
|
+
color: var(--menu-accent, #155290ff);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.context-menu button:active {
|
|
100
|
+
background: var(--menu-active, #e3eaf3);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.context-menu button[disabled] {
|
|
104
|
+
color: #aaa;
|
|
105
|
+
cursor: not-allowed;
|
|
106
|
+
background: none;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.context-menu-divider {
|
|
110
|
+
height: 1px;
|
|
111
|
+
background: var(--menu-divider, #c7d5eaff);
|
|
112
|
+
margin: 6px 12px;
|
|
113
|
+
border: none;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
canvas {
|
|
117
|
+
width: 100%;
|
|
118
|
+
height: 100%;
|
|
119
|
+
outline: none;
|
|
120
|
+
padding: 0;
|
|
121
|
+
margin: 0;
|
|
122
|
+
touch-action: none;
|
|
123
|
+
display: block;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.canvas-loader {
|
|
127
|
+
position: absolute;
|
|
128
|
+
top: 0;
|
|
129
|
+
left: 0;
|
|
130
|
+
display: flex;
|
|
131
|
+
flex-direction: column;
|
|
132
|
+
align-items: center;
|
|
133
|
+
justify-content: center;
|
|
134
|
+
background: rgba(255, 255, 255, 0.7);
|
|
135
|
+
z-index: 1000;
|
|
136
|
+
pointer-events: all;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.canvas-loader-spinner {
|
|
140
|
+
width: 48px;
|
|
141
|
+
height: 48px;
|
|
142
|
+
border: 6px solid #e0e0e0;
|
|
143
|
+
border-top: 6px solid #1976d2;
|
|
144
|
+
border-radius: 50%;
|
|
145
|
+
animation: canvas-loader-spin 1s linear infinite;
|
|
146
|
+
margin-bottom: 16px;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@keyframes canvas-loader-spin {
|
|
150
|
+
0% {
|
|
151
|
+
transform: rotate(0deg);
|
|
152
|
+
}
|
|
153
|
+
100% {
|
|
154
|
+
transform: rotate(360deg);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.canvas-loader-message {
|
|
159
|
+
font-size: 1.1rem;
|
|
160
|
+
color: #333;
|
|
161
|
+
background: rgba(255, 255, 255, 0.9);
|
|
162
|
+
padding: 8px 16px;
|
|
163
|
+
border-radius: 4px;
|
|
164
|
+
margin-top: 8px;
|
|
165
|
+
text-align: center;
|
|
166
|
+
max-width: 80%;
|
|
167
|
+
word-break: break-word;
|
|
168
|
+
}
|
|
169
|
+
`; }
|
|
170
|
+
#canvas;
|
|
171
|
+
#eventHub;
|
|
172
|
+
#resizeObserver;
|
|
173
|
+
#history;
|
|
174
|
+
#fileStorage;
|
|
175
|
+
#canvasStorage;
|
|
176
|
+
#saveFrequency;
|
|
177
|
+
#timeoutId;
|
|
178
|
+
#intervalId;
|
|
179
|
+
#onViewportResize;
|
|
180
|
+
#rootDiv;
|
|
181
|
+
#onChange;
|
|
182
|
+
#singleImageMenuOptions;
|
|
183
|
+
#multiImageMenuOptions;
|
|
184
|
+
#canvasImageMenuOptions;
|
|
61
185
|
get singleImageMenuOptions() {
|
|
62
|
-
return
|
|
186
|
+
return this.#singleImageMenuOptions;
|
|
63
187
|
}
|
|
64
188
|
get multiImageMenuOptions() {
|
|
65
|
-
return
|
|
189
|
+
return this.#multiImageMenuOptions;
|
|
66
190
|
}
|
|
67
191
|
get canvasImageMenuOptions() {
|
|
68
|
-
return
|
|
192
|
+
return this.#canvasImageMenuOptions;
|
|
69
193
|
}
|
|
70
194
|
get canvas() {
|
|
71
|
-
return
|
|
195
|
+
return this.#canvas;
|
|
72
196
|
}
|
|
73
197
|
get onCanvasChange() {
|
|
74
|
-
return
|
|
198
|
+
return this.#onChange;
|
|
75
199
|
}
|
|
76
200
|
set onCanvasChange(fn) {
|
|
77
|
-
|
|
201
|
+
this.#onChange = fn;
|
|
78
202
|
}
|
|
79
203
|
get eventHub() {
|
|
80
|
-
return
|
|
204
|
+
return this.#eventHub;
|
|
81
205
|
}
|
|
82
206
|
get rootDiv() {
|
|
83
|
-
return
|
|
207
|
+
return this.#rootDiv;
|
|
84
208
|
}
|
|
85
209
|
// Lifecycle
|
|
86
210
|
connectedCallback() {
|
|
@@ -90,9 +214,10 @@ let InfiniteCanvasElement = class InfiniteCanvasElement extends LitElement {
|
|
|
90
214
|
}
|
|
91
215
|
disconnectedCallback() {
|
|
92
216
|
document.removeEventListener("pointerdown", this.handleGlobalPointerDown, true);
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
217
|
+
window.removeEventListener("resize", this.#onViewportResize);
|
|
218
|
+
this.#resizeObserver?.disconnect();
|
|
219
|
+
this.#resizeObserver = undefined;
|
|
220
|
+
this.#canvas.destroy();
|
|
96
221
|
super.disconnectedCallback();
|
|
97
222
|
}
|
|
98
223
|
firstUpdated(_changedProperties) {
|
|
@@ -110,7 +235,7 @@ let InfiniteCanvasElement = class InfiniteCanvasElement extends LitElement {
|
|
|
110
235
|
changed.has("displayMode");
|
|
111
236
|
if (!sizeChanged)
|
|
112
237
|
return;
|
|
113
|
-
const container =
|
|
238
|
+
const container = this.#rootDiv;
|
|
114
239
|
const canvasEl = this.renderRoot.querySelector("canvas");
|
|
115
240
|
if (container && canvasEl) {
|
|
116
241
|
this.resizeCanvas(container, canvasEl);
|
|
@@ -118,12 +243,12 @@ let InfiniteCanvasElement = class InfiniteCanvasElement extends LitElement {
|
|
|
118
243
|
}
|
|
119
244
|
async initCanvas() {
|
|
120
245
|
await this.warmUpStorage();
|
|
121
|
-
|
|
122
|
-
|
|
246
|
+
this.#history = new CanvasHistory();
|
|
247
|
+
this.#eventHub = new EventEmitter();
|
|
123
248
|
const div = document.createElement("div");
|
|
124
249
|
div.style.overflow = "hidden";
|
|
125
250
|
this.renderRoot.appendChild(div);
|
|
126
|
-
|
|
251
|
+
this.#rootDiv = div;
|
|
127
252
|
const canvas = document.createElement("canvas");
|
|
128
253
|
// persistent state set up
|
|
129
254
|
this.assignFileStorage = this.assignFileStorage.bind(this);
|
|
@@ -148,7 +273,7 @@ let InfiniteCanvasElement = class InfiniteCanvasElement extends LitElement {
|
|
|
148
273
|
div.appendChild(canvas);
|
|
149
274
|
}
|
|
150
275
|
this.registerSignal();
|
|
151
|
-
|
|
276
|
+
this.#canvas = new Canvas(canvas, this.#history, this.#eventHub, this.debounceSaveToCanvasStorage, this.saveImageFileMetadata, this.getContainerSize);
|
|
152
277
|
try {
|
|
153
278
|
await this.restoreStateFromCanvasStorage();
|
|
154
279
|
}
|
|
@@ -157,21 +282,15 @@ let InfiniteCanvasElement = class InfiniteCanvasElement extends LitElement {
|
|
|
157
282
|
}
|
|
158
283
|
// resize canvas to start
|
|
159
284
|
this.resizeCanvas(div, canvas);
|
|
285
|
+
this.#onViewportResize = () => this.resizeCanvas(div, canvas);
|
|
286
|
+
window.addEventListener("resize", this.#onViewportResize);
|
|
160
287
|
const basicImageMenuOptions = createBasicImageMenuOptions.bind(this)();
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
288
|
+
this.#singleImageMenuOptions = createSingleImageMenuOptions.bind(this)(basicImageMenuOptions.options);
|
|
289
|
+
this.#canvasImageMenuOptions = createCanvasImageMenuOptions.bind(this)();
|
|
290
|
+
this.#multiImageMenuOptions = createMultiImageMenuOptions.bind(this)(basicImageMenuOptions.options);
|
|
164
291
|
this.dispatchEvent(new Event("load"));
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
if (!this.renderRoot.contains(stats.dom)) {
|
|
168
|
-
this.renderRoot.appendChild(stats.dom);
|
|
169
|
-
}
|
|
170
|
-
const animate = () => {
|
|
171
|
-
if (stats) {
|
|
172
|
-
stats.update();
|
|
173
|
-
}
|
|
174
|
-
__classPrivateFieldGet(this, _InfiniteCanvasElement_canvas, "f").render();
|
|
292
|
+
const animate = async () => {
|
|
293
|
+
this.#canvas.render();
|
|
175
294
|
requestAnimationFrame(animate);
|
|
176
295
|
};
|
|
177
296
|
animate();
|
|
@@ -200,6 +319,7 @@ let InfiniteCanvasElement = class InfiniteCanvasElement extends LitElement {
|
|
|
200
319
|
// set the canvas to be as big as possible so there will never be a flickering resize problem
|
|
201
320
|
let w = window.screen.width;
|
|
202
321
|
let h = window.screen.height;
|
|
322
|
+
const rect = container.getBoundingClientRect();
|
|
203
323
|
const targetWidthPx = Math.round(w * dpr);
|
|
204
324
|
const targetHeightPx = Math.round(h * dpr);
|
|
205
325
|
if (canvas.width !== targetWidthPx || canvas.height !== targetHeightPx) {
|
|
@@ -209,7 +329,6 @@ let InfiniteCanvasElement = class InfiniteCanvasElement extends LitElement {
|
|
|
209
329
|
canvas.style.width = `${w}px`;
|
|
210
330
|
canvas.style.height = `${h}px`;
|
|
211
331
|
// sets up camera dimensions
|
|
212
|
-
const rect = container.getBoundingClientRect();
|
|
213
332
|
this.canvas.camera.viewportX = rect.x;
|
|
214
333
|
this.canvas.camera.viewportY = rect.y;
|
|
215
334
|
this.canvas.camera.state.setHeight(rect.height);
|
|
@@ -217,25 +336,25 @@ let InfiniteCanvasElement = class InfiniteCanvasElement extends LitElement {
|
|
|
217
336
|
}
|
|
218
337
|
// Register signal
|
|
219
338
|
registerSignal() {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if (
|
|
226
|
-
|
|
339
|
+
this.#eventHub.on(LoaderEvent.start, showLoader.bind(this), "spinner");
|
|
340
|
+
this.#eventHub.on(LoaderEvent.done, hideLoader.bind(this));
|
|
341
|
+
this.#eventHub.on(ContextMenuEvent.Open, this.addContextMenu);
|
|
342
|
+
this.#eventHub.on(ContextMenuEvent.Close, this.clearContextMenu);
|
|
343
|
+
this.#eventHub.on(CanvasEvent.Change, () => {
|
|
344
|
+
if (this.#onChange)
|
|
345
|
+
this.#onChange();
|
|
227
346
|
});
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
347
|
+
this.#eventHub.on(SaveEvent.Save, this.saveToCanvasStorage);
|
|
348
|
+
this.#eventHub.on(SaveEvent.SaveCompleted, () => { });
|
|
349
|
+
this.#eventHub.on(SaveEvent.SaveFailed, () => console.error("Failed to Save!"));
|
|
231
350
|
}
|
|
232
351
|
// need to check if this is good practice
|
|
233
352
|
async warmUpStorage() {
|
|
234
|
-
if (!
|
|
235
|
-
|
|
353
|
+
if (!this.#fileStorage) {
|
|
354
|
+
this.#fileStorage = new DefaultIndexedDbStorage();
|
|
236
355
|
}
|
|
237
356
|
try {
|
|
238
|
-
await
|
|
357
|
+
await this.#fileStorage.readAll();
|
|
239
358
|
}
|
|
240
359
|
catch (err) {
|
|
241
360
|
console.error("Storage warm-up failed", err);
|
|
@@ -245,9 +364,9 @@ let InfiniteCanvasElement = class InfiniteCanvasElement extends LitElement {
|
|
|
245
364
|
* @returns An array containing the width and height of the container
|
|
246
365
|
*/
|
|
247
366
|
getContainerSize() {
|
|
248
|
-
if (!
|
|
367
|
+
if (!this.#rootDiv)
|
|
249
368
|
return;
|
|
250
|
-
const rect =
|
|
369
|
+
const rect = this.#rootDiv.getBoundingClientRect();
|
|
251
370
|
return [rect.width, rect.height];
|
|
252
371
|
}
|
|
253
372
|
// Storage & Persistence
|
|
@@ -255,17 +374,17 @@ let InfiniteCanvasElement = class InfiniteCanvasElement extends LitElement {
|
|
|
255
374
|
* @param storage Canvas storage stores the positions of all the renderables
|
|
256
375
|
* @param saveFrequency How often should auto save execute
|
|
257
376
|
*/
|
|
258
|
-
assignCanvasStorage(storage, saveFrequency =
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
377
|
+
assignCanvasStorage(storage, saveFrequency = this.#saveFrequency) {
|
|
378
|
+
this.#canvasStorage = storage;
|
|
379
|
+
this.#saveFrequency = saveFrequency;
|
|
380
|
+
this.#intervalId && clearInterval(this.#intervalId);
|
|
381
|
+
this.#intervalId = setInterval(this.saveToCanvasStorage, this.#saveFrequency);
|
|
263
382
|
}
|
|
264
383
|
/**
|
|
265
384
|
* @param storage File storage captures the information about the image data that has previously been added. Made more efficient by using SHA of the image data for storage.
|
|
266
385
|
*/
|
|
267
386
|
assignFileStorage(storage) {
|
|
268
|
-
|
|
387
|
+
this.#fileStorage = storage;
|
|
269
388
|
}
|
|
270
389
|
/**
|
|
271
390
|
* Duplicate images will not be written to the database
|
|
@@ -273,13 +392,13 @@ let InfiniteCanvasElement = class InfiniteCanvasElement extends LitElement {
|
|
|
273
392
|
* @returns The unique ID that the image has been logged with. This is a hashed version of the image data URL
|
|
274
393
|
*/
|
|
275
394
|
async saveImageFileMetadata(dataURL) {
|
|
276
|
-
if (!
|
|
277
|
-
|
|
395
|
+
if (!this.#fileStorage) {
|
|
396
|
+
this.#fileStorage = new DefaultIndexedDbStorage();
|
|
278
397
|
}
|
|
279
398
|
try {
|
|
280
399
|
const id = await hashStringToId(dataURL);
|
|
281
|
-
if (!(await
|
|
282
|
-
return await
|
|
400
|
+
if (!(await this.#fileStorage.checkIfImageStored(id))) {
|
|
401
|
+
return await this.#fileStorage.write(dataURL);
|
|
283
402
|
}
|
|
284
403
|
else {
|
|
285
404
|
return id;
|
|
@@ -294,11 +413,11 @@ let InfiniteCanvasElement = class InfiniteCanvasElement extends LitElement {
|
|
|
294
413
|
* @returns
|
|
295
414
|
*/
|
|
296
415
|
async getImageFileMetadata(fileId) {
|
|
297
|
-
if (!
|
|
298
|
-
|
|
416
|
+
if (!this.#fileStorage) {
|
|
417
|
+
this.#fileStorage = new DefaultIndexedDbStorage();
|
|
299
418
|
}
|
|
300
419
|
try {
|
|
301
|
-
return await
|
|
420
|
+
return await this.#fileStorage.read(fileId);
|
|
302
421
|
}
|
|
303
422
|
catch (err) {
|
|
304
423
|
console.error(err);
|
|
@@ -308,11 +427,11 @@ let InfiniteCanvasElement = class InfiniteCanvasElement extends LitElement {
|
|
|
308
427
|
* @returns All file metadata saved in connected storage
|
|
309
428
|
*/
|
|
310
429
|
async getAllImageFileMetdata() {
|
|
311
|
-
if (!
|
|
312
|
-
|
|
430
|
+
if (!this.#fileStorage) {
|
|
431
|
+
this.#fileStorage = new DefaultIndexedDbStorage();
|
|
313
432
|
}
|
|
314
433
|
try {
|
|
315
|
-
return await
|
|
434
|
+
return await this.#fileStorage.readAll();
|
|
316
435
|
}
|
|
317
436
|
catch (err) {
|
|
318
437
|
console.error(err);
|
|
@@ -322,134 +441,134 @@ let InfiniteCanvasElement = class InfiniteCanvasElement extends LitElement {
|
|
|
322
441
|
* Schedule the auto save to the canvas storage based on timer
|
|
323
442
|
*/
|
|
324
443
|
debounceSaveToCanvasStorage(timeout = 1000) {
|
|
325
|
-
if (!
|
|
326
|
-
|
|
444
|
+
if (!this.#canvasStorage) {
|
|
445
|
+
this.#canvasStorage = new DefaultLocalStorage(this.name);
|
|
327
446
|
}
|
|
328
|
-
clearTimeout(
|
|
329
|
-
|
|
447
|
+
clearTimeout(this.#timeoutId);
|
|
448
|
+
this.#timeoutId = setTimeout(this.saveToCanvasStorage, timeout);
|
|
330
449
|
}
|
|
331
450
|
async saveToCanvasStorage() {
|
|
332
|
-
if (!
|
|
333
|
-
|
|
451
|
+
if (!this.#canvasStorage) {
|
|
452
|
+
this.#canvasStorage = new DefaultLocalStorage(this.name);
|
|
334
453
|
}
|
|
335
|
-
|
|
336
|
-
.write(serializeCanvas(
|
|
337
|
-
.then(() =>
|
|
338
|
-
.catch(() =>
|
|
454
|
+
this.#canvasStorage
|
|
455
|
+
.write(serializeCanvas(this.#canvas))
|
|
456
|
+
.then(() => this.#eventHub.emit(SaveEvent.SaveCompleted))
|
|
457
|
+
.catch(() => this.#eventHub.emit(SaveEvent.SaveFailed));
|
|
339
458
|
}
|
|
340
459
|
async restoreStateFromCanvasStorage() {
|
|
341
|
-
if (!
|
|
342
|
-
|
|
460
|
+
if (!this.#canvasStorage) {
|
|
461
|
+
this.#canvasStorage = new DefaultLocalStorage(this.name);
|
|
343
462
|
}
|
|
344
|
-
const dataAsString = await
|
|
463
|
+
const dataAsString = await this.#canvasStorage.read();
|
|
345
464
|
const data = JSON.parse(dataAsString);
|
|
346
465
|
if (data) {
|
|
347
|
-
await deserializeCanvas(data,
|
|
466
|
+
await deserializeCanvas(data, this.#canvas, this.getImageFileMetadata);
|
|
348
467
|
}
|
|
349
468
|
}
|
|
350
469
|
// Canvas API
|
|
351
470
|
togglePointerMode() {
|
|
352
|
-
if (!
|
|
471
|
+
if (!this.#canvas)
|
|
353
472
|
return;
|
|
354
|
-
|
|
473
|
+
this.#canvas.changeMode();
|
|
355
474
|
}
|
|
356
475
|
toggleGrid() {
|
|
357
|
-
if (!
|
|
476
|
+
if (!this.#canvas)
|
|
358
477
|
return;
|
|
359
|
-
|
|
478
|
+
this.#canvas.toggleGrid();
|
|
360
479
|
}
|
|
361
480
|
zoomIn() {
|
|
362
|
-
if (!
|
|
481
|
+
if (!this.#canvas)
|
|
363
482
|
return;
|
|
364
|
-
|
|
483
|
+
this.#canvas.updateZoomByFixedAmount(-1);
|
|
365
484
|
}
|
|
366
485
|
zoomOut() {
|
|
367
|
-
if (!
|
|
486
|
+
if (!this.#canvas)
|
|
368
487
|
return;
|
|
369
|
-
|
|
488
|
+
this.#canvas.updateZoomByFixedAmount();
|
|
370
489
|
}
|
|
371
490
|
async addImages(fileList) {
|
|
372
|
-
if (!
|
|
491
|
+
if (!this.#canvas)
|
|
373
492
|
return;
|
|
374
|
-
const rect =
|
|
493
|
+
const rect = this.#rootDiv.getBoundingClientRect();
|
|
375
494
|
const clientX = rect.left + rect.width / 2;
|
|
376
495
|
const clientY = rect.top + rect.height / 2;
|
|
377
|
-
const [wx, wy] = getWorldCoords(clientX, clientY,
|
|
378
|
-
const newImages = await innerAddImages(fileList, (src) =>
|
|
379
|
-
|
|
496
|
+
const [wx, wy] = getWorldCoords(clientX, clientY, this.#canvas);
|
|
497
|
+
const newImages = await innerAddImages(fileList, (src) => this.#canvas.addImageToCanvas(src, wx, wy, 1, 1, true));
|
|
498
|
+
this.#history.push(makeMultiAddChildCommand(this.#canvas, newImages));
|
|
380
499
|
}
|
|
381
500
|
async removeImage(id) {
|
|
382
|
-
if (!
|
|
501
|
+
if (!this.#canvas)
|
|
383
502
|
return;
|
|
384
|
-
const child =
|
|
385
|
-
|
|
386
|
-
|
|
503
|
+
const child = this.#canvas.getChild(id);
|
|
504
|
+
this.#canvas.removeChild(child);
|
|
505
|
+
this.#history.push(makeRemoveChildCommand(this.#canvas, child));
|
|
387
506
|
}
|
|
388
507
|
/**
|
|
389
508
|
* @param url Currently only accept base64 url and the image is automatically added to screen center
|
|
390
509
|
*/
|
|
391
510
|
async addImageFromUrl(url) {
|
|
392
|
-
if (!
|
|
511
|
+
if (!this.#canvas)
|
|
393
512
|
return;
|
|
394
|
-
const rect =
|
|
513
|
+
const rect = this.#rootDiv.getBoundingClientRect();
|
|
395
514
|
const clientX = rect.left + rect.width / 2;
|
|
396
515
|
const clientY = rect.top + rect.height / 2;
|
|
397
|
-
const [wx, wy] = getWorldCoords(clientX, clientY,
|
|
398
|
-
const img = await
|
|
399
|
-
|
|
516
|
+
const [wx, wy] = getWorldCoords(clientX, clientY, this.#canvas);
|
|
517
|
+
const img = await this.#canvas.addImageToCanvas(url, wx, wy, 1, 1, true);
|
|
518
|
+
this.#history.push(makeMultiAddChildCommand(this.#canvas, [img]));
|
|
400
519
|
}
|
|
401
520
|
async copyImage() {
|
|
402
|
-
if (!
|
|
521
|
+
if (!this.#canvas)
|
|
403
522
|
return;
|
|
404
|
-
await copy(
|
|
523
|
+
await copy(this.#canvas.getSelected());
|
|
405
524
|
}
|
|
406
525
|
async pasteImage(e) {
|
|
407
|
-
if (!
|
|
526
|
+
if (!this.#canvas)
|
|
408
527
|
return;
|
|
409
|
-
await paste(e.clientX, e.clientY,
|
|
528
|
+
await paste(e.clientX, e.clientY, this.#canvas, this.#history, false);
|
|
410
529
|
}
|
|
411
530
|
flipVertical() {
|
|
412
|
-
if (!
|
|
531
|
+
if (!this.#canvas)
|
|
413
532
|
return;
|
|
414
|
-
|
|
533
|
+
this.#canvas.selectionManager.flip("vertical");
|
|
415
534
|
}
|
|
416
535
|
flipHorizontal() {
|
|
417
|
-
if (!
|
|
536
|
+
if (!this.#canvas)
|
|
418
537
|
return;
|
|
419
|
-
|
|
538
|
+
this.#canvas.selectionManager.flip("horizontal");
|
|
420
539
|
}
|
|
421
540
|
align(direction) {
|
|
422
|
-
if (!
|
|
541
|
+
if (!this.#canvas)
|
|
423
542
|
return;
|
|
424
|
-
|
|
543
|
+
this.#canvas.selectionManager.alignSelection(direction);
|
|
425
544
|
}
|
|
426
545
|
normalizeSelection(type, mode) {
|
|
427
|
-
if (!
|
|
546
|
+
if (!this.#canvas)
|
|
428
547
|
return;
|
|
429
|
-
|
|
548
|
+
this.#canvas.selectionManager.normalize(type, mode);
|
|
430
549
|
}
|
|
431
550
|
sendShapeToNewZOrder(toFront) {
|
|
432
|
-
if (!
|
|
551
|
+
if (!this.#canvas)
|
|
433
552
|
return;
|
|
434
|
-
|
|
553
|
+
this.#canvas.setShapeZOrder(toFront);
|
|
435
554
|
}
|
|
436
555
|
deleteSelectedImages() {
|
|
437
|
-
if (!
|
|
556
|
+
if (!this.#canvas)
|
|
438
557
|
return;
|
|
439
|
-
|
|
558
|
+
this.#canvas.selectionManager.deleteSelected(this.#canvas);
|
|
440
559
|
}
|
|
441
560
|
async exportCanvas(filename = "infinite-canvas.json") {
|
|
442
|
-
if (!
|
|
561
|
+
if (!this.#canvas)
|
|
443
562
|
return;
|
|
444
|
-
|
|
563
|
+
this.#eventHub.emit(LoaderEvent.start, "spinner");
|
|
445
564
|
const files = await this.getAllImageFileMetdata();
|
|
446
|
-
const data = serializeCanvas(
|
|
565
|
+
const data = serializeCanvas(this.#canvas, files);
|
|
447
566
|
downloadJSON(filename, data);
|
|
448
|
-
|
|
567
|
+
this.#eventHub.emit(LoaderEvent.done);
|
|
449
568
|
}
|
|
450
569
|
async importCanvas(fileList) {
|
|
451
|
-
|
|
452
|
-
if (!
|
|
570
|
+
this.#eventHub.emit(LoaderEvent.start, "spinner");
|
|
571
|
+
if (!this.#canvas)
|
|
453
572
|
return;
|
|
454
573
|
if (!fileList || fileList.length !== 1)
|
|
455
574
|
return;
|
|
@@ -459,181 +578,32 @@ let InfiniteCanvasElement = class InfiniteCanvasElement extends LitElement {
|
|
|
459
578
|
!file.name.toLowerCase().endsWith(".json")))
|
|
460
579
|
return;
|
|
461
580
|
const data = await readJSONFile(file);
|
|
462
|
-
await deserializeCanvas(data,
|
|
463
|
-
|
|
464
|
-
|
|
581
|
+
await deserializeCanvas(data, this.#canvas, this.getImageFileMetadata, this.saveImageFileMetadata);
|
|
582
|
+
this.#eventHub.emit(SaveEvent.Save);
|
|
583
|
+
this.#eventHub.emit(LoaderEvent.done);
|
|
465
584
|
}
|
|
466
585
|
clearCanvas() {
|
|
467
|
-
if (!
|
|
586
|
+
if (!this.#canvas)
|
|
468
587
|
return;
|
|
469
|
-
|
|
588
|
+
this.#canvas.clearChildren();
|
|
470
589
|
}
|
|
471
590
|
/**
|
|
472
591
|
* A non-reactive method that captures the number of images added to the canvas
|
|
473
592
|
*/
|
|
474
593
|
getTotalNumberOfChildren() {
|
|
475
|
-
if (!
|
|
594
|
+
if (!this.#canvas)
|
|
476
595
|
return;
|
|
477
|
-
return
|
|
596
|
+
return this.#canvas.totalNumberOfChildren;
|
|
478
597
|
}
|
|
479
598
|
/**
|
|
480
599
|
* A non-reactive method that captures the number of images that are in camera
|
|
481
600
|
*/
|
|
482
601
|
getNumberOfChildrenRendered() {
|
|
483
|
-
if (!
|
|
602
|
+
if (!this.#canvas)
|
|
484
603
|
return;
|
|
485
|
-
return
|
|
604
|
+
return this.#canvas.numberOfChildrenRendered;
|
|
486
605
|
}
|
|
487
606
|
};
|
|
488
|
-
_InfiniteCanvasElement_canvas = new WeakMap();
|
|
489
|
-
_InfiniteCanvasElement_eventHub = new WeakMap();
|
|
490
|
-
_InfiniteCanvasElement_resizeObserver = new WeakMap();
|
|
491
|
-
_InfiniteCanvasElement_history = new WeakMap();
|
|
492
|
-
_InfiniteCanvasElement_fileStorage = new WeakMap();
|
|
493
|
-
_InfiniteCanvasElement_canvasStorage = new WeakMap();
|
|
494
|
-
_InfiniteCanvasElement_saveFrequency = new WeakMap();
|
|
495
|
-
_InfiniteCanvasElement_timeoutId = new WeakMap();
|
|
496
|
-
_InfiniteCanvasElement_intervalId = new WeakMap();
|
|
497
|
-
_InfiniteCanvasElement_rootDiv = new WeakMap();
|
|
498
|
-
_InfiniteCanvasElement_onChange = new WeakMap();
|
|
499
|
-
_InfiniteCanvasElement_singleImageMenuOptions = new WeakMap();
|
|
500
|
-
_InfiniteCanvasElement_multiImageMenuOptions = new WeakMap();
|
|
501
|
-
_InfiniteCanvasElement_canvasImageMenuOptions = new WeakMap();
|
|
502
|
-
InfiniteCanvasElement.properties = {
|
|
503
|
-
name: { type: String, reflect: true },
|
|
504
|
-
width: { type: String, reflect: true },
|
|
505
|
-
height: { type: String, reflect: true },
|
|
506
|
-
displayMode: { type: String, reflect: true },
|
|
507
|
-
};
|
|
508
|
-
InfiniteCanvasElement.styles = css `
|
|
509
|
-
:host {
|
|
510
|
-
position: relative;
|
|
511
|
-
overflow: hidden;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
.context-menu {
|
|
515
|
-
position: absolute;
|
|
516
|
-
background: white;
|
|
517
|
-
min-width: 180px;
|
|
518
|
-
background: var(--menu-bg, #fff);
|
|
519
|
-
border-radius: 6px;
|
|
520
|
-
border: 1px solid var(--menu-border, #9f9f9fff);
|
|
521
|
-
box-sizing: border-box;
|
|
522
|
-
padding: 6px 0;
|
|
523
|
-
display: flex;
|
|
524
|
-
gap: 2px;
|
|
525
|
-
flex-direction: column;
|
|
526
|
-
font-family: system-ui, sans-serif;
|
|
527
|
-
animation: fadeInMenu 0.13s cubic-bezier(0.4, 0, 0.2, 1);
|
|
528
|
-
overflow: scroll;
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
@keyframes fadeInMenu {
|
|
532
|
-
from {
|
|
533
|
-
opacity: 0;
|
|
534
|
-
transform: translateY(8px);
|
|
535
|
-
}
|
|
536
|
-
to {
|
|
537
|
-
opacity: 1;
|
|
538
|
-
transform: none;
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
.context-menu button {
|
|
543
|
-
all: unset;
|
|
544
|
-
display: flex;
|
|
545
|
-
align-items: center;
|
|
546
|
-
box-sizing: border-box;
|
|
547
|
-
width: 100%;
|
|
548
|
-
padding: 8px 18px;
|
|
549
|
-
font-size: 15px;
|
|
550
|
-
color: var(--menu-fg, #222);
|
|
551
|
-
background: none;
|
|
552
|
-
cursor: pointer;
|
|
553
|
-
transition:
|
|
554
|
-
background 0.1s,
|
|
555
|
-
color 0.1s;
|
|
556
|
-
user-select: none;
|
|
557
|
-
outline: none;
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
.context-menu button:hover,
|
|
561
|
-
.context-menu button:focus-visible {
|
|
562
|
-
background: var(--menu-hover, #c7d5eaff);
|
|
563
|
-
color: var(--menu-accent, #155290ff);
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
.context-menu button:active {
|
|
567
|
-
background: var(--menu-active, #e3eaf3);
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
.context-menu button[disabled] {
|
|
571
|
-
color: #aaa;
|
|
572
|
-
cursor: not-allowed;
|
|
573
|
-
background: none;
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
.context-menu-divider {
|
|
577
|
-
height: 1px;
|
|
578
|
-
background: var(--menu-divider, #c7d5eaff);
|
|
579
|
-
margin: 6px 12px;
|
|
580
|
-
border: none;
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
canvas {
|
|
584
|
-
width: 100%;
|
|
585
|
-
height: 100%;
|
|
586
|
-
outline: none;
|
|
587
|
-
padding: 0;
|
|
588
|
-
margin: 0;
|
|
589
|
-
touch-action: none;
|
|
590
|
-
display: block;
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
.canvas-loader {
|
|
594
|
-
position: absolute;
|
|
595
|
-
top: 0;
|
|
596
|
-
left: 0;
|
|
597
|
-
display: flex;
|
|
598
|
-
flex-direction: column;
|
|
599
|
-
align-items: center;
|
|
600
|
-
justify-content: center;
|
|
601
|
-
background: rgba(255, 255, 255, 0.7);
|
|
602
|
-
z-index: 1000;
|
|
603
|
-
pointer-events: all;
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
.canvas-loader-spinner {
|
|
607
|
-
width: 48px;
|
|
608
|
-
height: 48px;
|
|
609
|
-
border: 6px solid #e0e0e0;
|
|
610
|
-
border-top: 6px solid #1976d2;
|
|
611
|
-
border-radius: 50%;
|
|
612
|
-
animation: canvas-loader-spin 1s linear infinite;
|
|
613
|
-
margin-bottom: 16px;
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
@keyframes canvas-loader-spin {
|
|
617
|
-
0% {
|
|
618
|
-
transform: rotate(0deg);
|
|
619
|
-
}
|
|
620
|
-
100% {
|
|
621
|
-
transform: rotate(360deg);
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
.canvas-loader-message {
|
|
626
|
-
font-size: 1.1rem;
|
|
627
|
-
color: #333;
|
|
628
|
-
background: rgba(255, 255, 255, 0.9);
|
|
629
|
-
padding: 8px 16px;
|
|
630
|
-
border-radius: 4px;
|
|
631
|
-
margin-top: 8px;
|
|
632
|
-
text-align: center;
|
|
633
|
-
max-width: 80%;
|
|
634
|
-
word-break: break-word;
|
|
635
|
-
}
|
|
636
|
-
`;
|
|
637
607
|
InfiniteCanvasElement = __decorate([
|
|
638
608
|
customElement("infinite-canvas")
|
|
639
609
|
], InfiniteCanvasElement);
|