@kittl/sdk 0.0.1
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/LICENSE +201 -0
- package/README.md +13 -0
- package/dist/kittl-sdk.esm.js +709 -0
- package/dist/kittl-sdk.js +708 -0
- package/dist/sdk.d.ts +1916 -0
- package/package.json +26 -0
|
@@ -0,0 +1,708 @@
|
|
|
1
|
+
// @ts-self-types="./sdk.d.ts"
|
|
2
|
+
/*! Kittl SDK - https://kittl.com - see license for terms */
|
|
3
|
+
|
|
4
|
+
"use strict";
|
|
5
|
+
(() => {
|
|
6
|
+
// ../../packages/editor-core/sdk/sdk-bridge/dist/bridge/types/toExtensions.js
|
|
7
|
+
var isSDKResponseMessage = (data) => {
|
|
8
|
+
return typeof data === "object" && data !== null && "command" in data && data.command === "sdk-response" && "messageId" in data && typeof data.messageId === "string";
|
|
9
|
+
};
|
|
10
|
+
var isStatelessPushMessage = (data) => {
|
|
11
|
+
return typeof data === "object" && data !== null && "command" in data && data.command === "stateless-message" && "type" in data && typeof data.type === "string" && "payload" in data && typeof data.payload === "object" && data.payload !== null;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// ../../packages/editor-core/sdk/sdk-bridge/dist/bridge/shared/createApiProxyCore.js
|
|
15
|
+
var buildMethodPath = (rootPrefix, pathSegments) => {
|
|
16
|
+
if (!rootPrefix) {
|
|
17
|
+
return pathSegments.join(".");
|
|
18
|
+
}
|
|
19
|
+
if (pathSegments.length === 0) {
|
|
20
|
+
return rootPrefix;
|
|
21
|
+
}
|
|
22
|
+
return `${rootPrefix}.${pathSegments.join(".")}`;
|
|
23
|
+
};
|
|
24
|
+
var createCallablePathProxy = (options) => {
|
|
25
|
+
const createNode = (pathSegments) => {
|
|
26
|
+
const target = () => {
|
|
27
|
+
};
|
|
28
|
+
const handler = {
|
|
29
|
+
get: (_target, prop) => {
|
|
30
|
+
if (typeof prop === "symbol") {
|
|
31
|
+
return Reflect.get(target, prop);
|
|
32
|
+
}
|
|
33
|
+
return createNode([...pathSegments, String(prop)]);
|
|
34
|
+
},
|
|
35
|
+
apply: (_target, _thisArg, args) => {
|
|
36
|
+
const context = {
|
|
37
|
+
pathSegments,
|
|
38
|
+
methodPath: buildMethodPath(options.rootPrefix ?? "", pathSegments),
|
|
39
|
+
args
|
|
40
|
+
};
|
|
41
|
+
options.beforeInvoke?.(context);
|
|
42
|
+
return options.onInvoke(context);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
return new Proxy(target, handler);
|
|
46
|
+
};
|
|
47
|
+
return createNode([]);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// ../../packages/editor-core/sdk/sdk-bridge/dist/bridge/shared/index.js
|
|
51
|
+
var EDITOR_URL = typeof window !== "undefined" ? window.location?.origin : "";
|
|
52
|
+
|
|
53
|
+
// ../../packages/editor-core/sdk/sdk-bridge/dist/bridge/sandboxed/utils/approvedDomain/approvedDomain.js
|
|
54
|
+
var parentDomain;
|
|
55
|
+
var APPROVED_EXTENSION_COMMUNICATION_DOMAINS = [
|
|
56
|
+
"https://app.stagingdesigner.com",
|
|
57
|
+
"https://*.stagingdesigner.com",
|
|
58
|
+
"http://localhost:3000",
|
|
59
|
+
"https://app.kittl.com"
|
|
60
|
+
];
|
|
61
|
+
var toOrigin = (value) => {
|
|
62
|
+
if (!value) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
return new URL(value).origin;
|
|
67
|
+
} catch (_error) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
var escapeRegex = (value) => {
|
|
72
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
73
|
+
};
|
|
74
|
+
var matchesApprovedDomain = (origin, approvedDomain) => {
|
|
75
|
+
const normalized = approvedDomain.trim();
|
|
76
|
+
if (!normalized) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
if (normalized.includes("*")) {
|
|
80
|
+
const wildcardRegex = new RegExp(
|
|
81
|
+
// because we use wildcards only in the middle of the string
|
|
82
|
+
// we know that our list must start with the regex and end with the regex
|
|
83
|
+
// so we add the ^ and $ to the regex too
|
|
84
|
+
`^${escapeRegex(normalized).replace(/\\\*/g, ".*")}$`
|
|
85
|
+
);
|
|
86
|
+
return wildcardRegex.test(origin);
|
|
87
|
+
}
|
|
88
|
+
return origin === normalized;
|
|
89
|
+
};
|
|
90
|
+
var getParentDomain = () => {
|
|
91
|
+
if (parentDomain) {
|
|
92
|
+
return parentDomain;
|
|
93
|
+
}
|
|
94
|
+
const referrer = window.document.referrer;
|
|
95
|
+
const ancestorOrigins = window.location.ancestorOrigins;
|
|
96
|
+
const ancestorOrigin = ancestorOrigins && ancestorOrigins.length > 0 ? ancestorOrigins[ancestorOrigins.length - 1] : void 0;
|
|
97
|
+
const currentDomainOrigin = toOrigin(referrer) ?? toOrigin(ancestorOrigin);
|
|
98
|
+
if (!currentDomainOrigin) {
|
|
99
|
+
throw new Error("No current domain origin found");
|
|
100
|
+
}
|
|
101
|
+
const approvedParentDomain = APPROVED_EXTENSION_COMMUNICATION_DOMAINS.find(
|
|
102
|
+
(domain) => {
|
|
103
|
+
return matchesApprovedDomain(currentDomainOrigin, domain);
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
if (!approvedParentDomain) {
|
|
107
|
+
throw new Error("No approved parent domain found");
|
|
108
|
+
}
|
|
109
|
+
parentDomain = currentDomainOrigin;
|
|
110
|
+
return parentDomain;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// ../../packages/editor-core/sdk/sdk-bridge/dist/bridge/sandboxed/messageSender.js
|
|
114
|
+
var pendingRequests = /* @__PURE__ */ new Map();
|
|
115
|
+
var generateMessageId = () => {
|
|
116
|
+
if (crypto?.randomUUID && typeof crypto.randomUUID === "function") {
|
|
117
|
+
return crypto.randomUUID();
|
|
118
|
+
}
|
|
119
|
+
return Math.random().toString(36).substring(7);
|
|
120
|
+
};
|
|
121
|
+
var sendMessage = (message) => {
|
|
122
|
+
window.parent.postMessage(message, getParentDomain());
|
|
123
|
+
return new Promise((resolve, reject) => {
|
|
124
|
+
pendingRequests.set(message.messageId, {
|
|
125
|
+
resolve: (value) => {
|
|
126
|
+
resolve(value);
|
|
127
|
+
},
|
|
128
|
+
reject
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
};
|
|
132
|
+
var sendLoaded = () => {
|
|
133
|
+
const parent = window.parent;
|
|
134
|
+
if (!parent || parent === window) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const message = { command: "loaded" };
|
|
138
|
+
parent.postMessage(message, getParentDomain());
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// ../../packages/editor-core/sdk/sdk-bridge/dist/bridge/sandboxed/handlers/apiProxy/index.js
|
|
142
|
+
var createSDKCallMessage = {
|
|
143
|
+
ai: (dots, args, messageId) => ({
|
|
144
|
+
command: "sdk-call",
|
|
145
|
+
dots,
|
|
146
|
+
args,
|
|
147
|
+
messageId,
|
|
148
|
+
apiType: "ai"
|
|
149
|
+
}),
|
|
150
|
+
context: (dots, args, messageId) => ({
|
|
151
|
+
command: "sdk-call",
|
|
152
|
+
dots,
|
|
153
|
+
args,
|
|
154
|
+
messageId,
|
|
155
|
+
apiType: "context"
|
|
156
|
+
}),
|
|
157
|
+
design: (dots, args, messageId) => ({
|
|
158
|
+
command: "sdk-call",
|
|
159
|
+
dots,
|
|
160
|
+
args,
|
|
161
|
+
messageId,
|
|
162
|
+
apiType: "design"
|
|
163
|
+
}),
|
|
164
|
+
font: (dots, args, messageId) => ({
|
|
165
|
+
command: "sdk-call",
|
|
166
|
+
dots,
|
|
167
|
+
args,
|
|
168
|
+
messageId,
|
|
169
|
+
apiType: "font"
|
|
170
|
+
}),
|
|
171
|
+
state: (dots, args, messageId) => ({
|
|
172
|
+
command: "sdk-call",
|
|
173
|
+
dots,
|
|
174
|
+
args,
|
|
175
|
+
messageId,
|
|
176
|
+
apiType: "state"
|
|
177
|
+
}),
|
|
178
|
+
upload: (dots, args, messageId) => ({
|
|
179
|
+
command: "sdk-call",
|
|
180
|
+
dots,
|
|
181
|
+
args,
|
|
182
|
+
messageId,
|
|
183
|
+
apiType: "upload"
|
|
184
|
+
})
|
|
185
|
+
};
|
|
186
|
+
function createApiProxy(apiType) {
|
|
187
|
+
return createCallablePathProxy({
|
|
188
|
+
onInvoke: ({ pathSegments, args }) => {
|
|
189
|
+
return sendMessage(
|
|
190
|
+
createSDKCallMessage[apiType](
|
|
191
|
+
[...pathSegments],
|
|
192
|
+
[...args],
|
|
193
|
+
generateMessageId()
|
|
194
|
+
)
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ../../packages/editor-core/sdk/sdk-bridge/dist/bridge/sandboxed/handlers/apiProxy/aiApiProxy.js
|
|
201
|
+
function createAIApiProxy() {
|
|
202
|
+
return {
|
|
203
|
+
spendCredits: async (options) => {
|
|
204
|
+
const { onAccept, onDecline, ...serializable } = options;
|
|
205
|
+
const message = {
|
|
206
|
+
command: "sdk-call",
|
|
207
|
+
dots: ["spendCredits"],
|
|
208
|
+
args: [serializable],
|
|
209
|
+
messageId: generateMessageId(),
|
|
210
|
+
apiType: "ai"
|
|
211
|
+
};
|
|
212
|
+
const response = await sendMessage(message);
|
|
213
|
+
if (response.isOk && response.result.success) {
|
|
214
|
+
onAccept?.();
|
|
215
|
+
} else {
|
|
216
|
+
onDecline?.();
|
|
217
|
+
}
|
|
218
|
+
return response;
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ../../packages/editor-core/sdk/sdk-bridge/dist/bridge/sandboxed/handlers/authBridge/index.js
|
|
224
|
+
var hashPKCECode = async (input) => {
|
|
225
|
+
const textAsBuffer = new TextEncoder().encode(input);
|
|
226
|
+
const hashBuffer = await window.crypto.subtle.digest("SHA-256", textAsBuffer);
|
|
227
|
+
const hashArray = new Uint8Array(hashBuffer);
|
|
228
|
+
const binaryString = String.fromCharCode(...hashArray);
|
|
229
|
+
const base64 = btoa(binaryString);
|
|
230
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
231
|
+
};
|
|
232
|
+
var generatePKCECode = () => {
|
|
233
|
+
if (!window.crypto) {
|
|
234
|
+
throw new Error("Crypto API not available");
|
|
235
|
+
}
|
|
236
|
+
const array = new Uint32Array(56);
|
|
237
|
+
window.crypto.getRandomValues(array);
|
|
238
|
+
return Array.from(array, (dec) => `0${dec.toString(16)}`.slice(-2)).join("");
|
|
239
|
+
};
|
|
240
|
+
var createAuthBridgeApi = () => {
|
|
241
|
+
return {
|
|
242
|
+
startAuth: async (request) => {
|
|
243
|
+
const state = generateMessageId();
|
|
244
|
+
let pkceCode = "";
|
|
245
|
+
let code_verifier = "";
|
|
246
|
+
if (request.generatePKCE) {
|
|
247
|
+
code_verifier = generatePKCECode();
|
|
248
|
+
pkceCode = await hashPKCECode(code_verifier);
|
|
249
|
+
}
|
|
250
|
+
const message = {
|
|
251
|
+
command: "auth-start",
|
|
252
|
+
messageId: generateMessageId(),
|
|
253
|
+
state,
|
|
254
|
+
provider: request.provider,
|
|
255
|
+
params: {
|
|
256
|
+
...request.params,
|
|
257
|
+
...pkceCode ? {
|
|
258
|
+
code_challenge: pkceCode,
|
|
259
|
+
code_challenge_method: "S256"
|
|
260
|
+
} : {}
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
const response = await sendMessage(message);
|
|
264
|
+
if (!response.isOk) {
|
|
265
|
+
return response;
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
...response,
|
|
269
|
+
result: {
|
|
270
|
+
...response.result,
|
|
271
|
+
code_verifier
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
},
|
|
275
|
+
exchangeCode: (request) => {
|
|
276
|
+
const message = {
|
|
277
|
+
command: "auth-exchange",
|
|
278
|
+
messageId: generateMessageId(),
|
|
279
|
+
code: request.code,
|
|
280
|
+
code_verifier: request.code_verifier,
|
|
281
|
+
provider: request.provider,
|
|
282
|
+
params: request.params,
|
|
283
|
+
headers: request.headers
|
|
284
|
+
};
|
|
285
|
+
return sendMessage(message);
|
|
286
|
+
},
|
|
287
|
+
getAuthToken: (request) => {
|
|
288
|
+
const message = {
|
|
289
|
+
command: "auth-get-token",
|
|
290
|
+
messageId: generateMessageId(),
|
|
291
|
+
provider: request.provider
|
|
292
|
+
};
|
|
293
|
+
return sendMessage(message);
|
|
294
|
+
},
|
|
295
|
+
refreshToken: (request) => {
|
|
296
|
+
const message = {
|
|
297
|
+
command: "auth-refresh",
|
|
298
|
+
messageId: generateMessageId(),
|
|
299
|
+
provider: request.provider,
|
|
300
|
+
refresh_token: request.refresh_token
|
|
301
|
+
};
|
|
302
|
+
return sendMessage(message);
|
|
303
|
+
},
|
|
304
|
+
logout: (request) => {
|
|
305
|
+
const message = {
|
|
306
|
+
command: "auth-logout",
|
|
307
|
+
messageId: generateMessageId(),
|
|
308
|
+
provider: request.provider
|
|
309
|
+
};
|
|
310
|
+
return sendMessage(message);
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// ../../packages/editor-core/sdk/sdk-bridge/dist/bridge/sandboxed/handlers/stateless/index.js
|
|
316
|
+
var createStatelessBridgeApi = () => {
|
|
317
|
+
const subscribers = /* @__PURE__ */ new Map();
|
|
318
|
+
return {
|
|
319
|
+
send: (message) => {
|
|
320
|
+
const messageId = generateMessageId();
|
|
321
|
+
const bridgeMessage = {
|
|
322
|
+
command: "stateless-send",
|
|
323
|
+
messageId,
|
|
324
|
+
type: message.type,
|
|
325
|
+
payload: message.payload
|
|
326
|
+
};
|
|
327
|
+
return sendMessage(bridgeMessage);
|
|
328
|
+
},
|
|
329
|
+
subscribe: async (key, handler) => {
|
|
330
|
+
let handlers = subscribers.get(key);
|
|
331
|
+
if (!handlers) {
|
|
332
|
+
handlers = /* @__PURE__ */ new Set();
|
|
333
|
+
subscribers.set(key, handlers);
|
|
334
|
+
}
|
|
335
|
+
handlers.add(handler);
|
|
336
|
+
const messageId = generateMessageId();
|
|
337
|
+
const subscribeMessage = {
|
|
338
|
+
command: "stateless-subscribe",
|
|
339
|
+
messageId,
|
|
340
|
+
key
|
|
341
|
+
};
|
|
342
|
+
return sendMessage(subscribeMessage);
|
|
343
|
+
},
|
|
344
|
+
unsubscribe: async (key) => {
|
|
345
|
+
subscribers.delete(key);
|
|
346
|
+
const messageId = generateMessageId();
|
|
347
|
+
const unsubscribeMessage = {
|
|
348
|
+
command: "stateless-unsubscribe",
|
|
349
|
+
messageId,
|
|
350
|
+
key
|
|
351
|
+
};
|
|
352
|
+
return sendMessage(unsubscribeMessage);
|
|
353
|
+
},
|
|
354
|
+
dispatch: (type, payload) => {
|
|
355
|
+
const handlers = subscribers.get(type);
|
|
356
|
+
if (handlers) {
|
|
357
|
+
for (const handler of handlers) {
|
|
358
|
+
handler(payload);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
// ../../packages/editor-core/sdk/sdk-bridge/dist/bridge/sandboxed/handlers.js
|
|
366
|
+
var statelessApi = createStatelessBridgeApi();
|
|
367
|
+
var handleIncomingMessage = (event) => {
|
|
368
|
+
const parent = window.parent;
|
|
369
|
+
if (!parent || parent === window) {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (event.source !== parent) {
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const { data } = event;
|
|
376
|
+
if (isSDKResponseMessage(data)) {
|
|
377
|
+
const resolver = pendingRequests.get(data.messageId);
|
|
378
|
+
if (resolver) {
|
|
379
|
+
pendingRequests.delete(data.messageId);
|
|
380
|
+
const response = data.response;
|
|
381
|
+
resolver.resolve(response);
|
|
382
|
+
}
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
if (isStatelessPushMessage(data)) {
|
|
386
|
+
statelessApi.dispatch(data.type, data.payload);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
var bridgeHandlers = {
|
|
391
|
+
ai: createAIApiProxy(),
|
|
392
|
+
context: createApiProxy("context"),
|
|
393
|
+
design: createApiProxy("design"),
|
|
394
|
+
font: createApiProxy("font"),
|
|
395
|
+
state: createApiProxy("state"),
|
|
396
|
+
upload: createApiProxy("upload"),
|
|
397
|
+
auth: createAuthBridgeApi(),
|
|
398
|
+
stateless: statelessApi
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
// src/api/utils.ts
|
|
402
|
+
function unwrapResult(result) {
|
|
403
|
+
if (result.isOk) {
|
|
404
|
+
return result.result;
|
|
405
|
+
}
|
|
406
|
+
throw result.error;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// src/api/dragAndDrop/DragAndDropApi.ts
|
|
410
|
+
var HIDDEN_DRAG_PREVIEW_OFFSET_PX = "-9999px";
|
|
411
|
+
var DragAndDropApi = class {
|
|
412
|
+
/**
|
|
413
|
+
* Creates the drag-and-drop API from bridged SDK dependencies.
|
|
414
|
+
*/
|
|
415
|
+
constructor(dependencies) {
|
|
416
|
+
this.state = dependencies.state;
|
|
417
|
+
this.context = dependencies.context;
|
|
418
|
+
this.design = dependencies.design;
|
|
419
|
+
this.upload = dependencies.upload;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Handles the extension-side `dragstart` event and attaches a one-time
|
|
423
|
+
* `dragend` listener that completes the Kittl drop flow.
|
|
424
|
+
*/
|
|
425
|
+
startDragImageEvent(input) {
|
|
426
|
+
const metrics = this.captureDragMetrics(input);
|
|
427
|
+
this.applyNativeDragSetup(input.event.dataTransfer, input.img, metrics);
|
|
428
|
+
const handleDragEnd = (event) => {
|
|
429
|
+
void this.handleManagedDragEnd(metrics, event);
|
|
430
|
+
};
|
|
431
|
+
input.img.addEventListener("dragend", handleDragEnd, { once: true });
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Inserts the dropped image at the computed drop position. When
|
|
435
|
+
* `input.persistImage === true`, fetches `src`, uploads it, and replaces
|
|
436
|
+
* the inserted object with the uploaded asset.
|
|
437
|
+
*/
|
|
438
|
+
async dropImage(input) {
|
|
439
|
+
let objectId = null;
|
|
440
|
+
try {
|
|
441
|
+
const offset = unwrapResult(await this.context.get()).dragAndDrop.offset;
|
|
442
|
+
const params = this.buildDropImageParams(input, offset);
|
|
443
|
+
objectId = await this.addDroppedImage(params);
|
|
444
|
+
if (input.persistImage) {
|
|
445
|
+
await this.persistDroppedImage({
|
|
446
|
+
objectId,
|
|
447
|
+
src: input.src
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
} catch {
|
|
451
|
+
}
|
|
452
|
+
return objectId;
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Adds the dropped image to the design and returns the inserted object id.
|
|
456
|
+
*/
|
|
457
|
+
async addDroppedImage(params) {
|
|
458
|
+
const result = await this.design.image.addImage(params);
|
|
459
|
+
const insertedImage = unwrapResult(result);
|
|
460
|
+
if (!insertedImage?.id) {
|
|
461
|
+
throw new Error("Failed to add dropped image");
|
|
462
|
+
}
|
|
463
|
+
return insertedImage.id;
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Fetches `src`, uploads the bytes, and replaces the inserted object with
|
|
467
|
+
* the uploaded Kittl asset when persistence succeeds.
|
|
468
|
+
*/
|
|
469
|
+
async persistDroppedImage(input) {
|
|
470
|
+
const response = await fetch(input.src);
|
|
471
|
+
if (!response.ok) {
|
|
472
|
+
throw new Error(`Failed to fetch dropped image: ${response.status}`);
|
|
473
|
+
}
|
|
474
|
+
const blob = await response.blob();
|
|
475
|
+
const uploadResult = await this.upload.image.upload({ blob });
|
|
476
|
+
const uploadedFiles = unwrapResult(uploadResult);
|
|
477
|
+
if (uploadedFiles.length === 0) {
|
|
478
|
+
throw new Error("Failed to upload dropped image");
|
|
479
|
+
}
|
|
480
|
+
await this.replaceWithUploadedImage({
|
|
481
|
+
objectId: input.objectId,
|
|
482
|
+
objectName: uploadedFiles[0].objectName
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Replaces the inserted image with the uploaded asset if the object still
|
|
487
|
+
* exists.
|
|
488
|
+
*/
|
|
489
|
+
async replaceWithUploadedImage(input) {
|
|
490
|
+
const objectResult = await this.design.object.getObject({
|
|
491
|
+
id: input.objectId
|
|
492
|
+
});
|
|
493
|
+
if (!objectResult.isOk) {
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
await this.design.image.updateImage({
|
|
497
|
+
id: input.objectId,
|
|
498
|
+
objectName: input.objectName
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Converts iframe drag coordinates into `addImage(...)` params, applying host
|
|
503
|
+
* offsets, grab offsets, and optional rendered size.
|
|
504
|
+
*/
|
|
505
|
+
buildDropImageParams(input, offset) {
|
|
506
|
+
const grabOffsetX = input.grabOffsetX ?? 0;
|
|
507
|
+
const grabOffsetY = input.grabOffsetY ?? 0;
|
|
508
|
+
const baseX = input.x + offset.left;
|
|
509
|
+
const baseY = input.y + offset.top;
|
|
510
|
+
const dropX = Math.floor(baseX - grabOffsetX);
|
|
511
|
+
const dropY = Math.floor(baseY - grabOffsetY);
|
|
512
|
+
const baseParams = {
|
|
513
|
+
src: input.src.trim(),
|
|
514
|
+
position: {
|
|
515
|
+
relative: {
|
|
516
|
+
to: "screen",
|
|
517
|
+
coordinates: {
|
|
518
|
+
x: dropX,
|
|
519
|
+
y: dropY
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
const { imgWidth, imgHeight } = input;
|
|
525
|
+
if (typeof imgWidth !== "number" || typeof imgHeight !== "number") {
|
|
526
|
+
return baseParams;
|
|
527
|
+
}
|
|
528
|
+
return {
|
|
529
|
+
...baseParams,
|
|
530
|
+
size: {
|
|
531
|
+
width: imgWidth,
|
|
532
|
+
height: imgHeight,
|
|
533
|
+
applyViewportScale: true
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Captures the rendered image size and pointer grab point at drag start so
|
|
539
|
+
* the native drag preview and drop placement stay aligned.
|
|
540
|
+
*/
|
|
541
|
+
captureDragMetrics(input) {
|
|
542
|
+
const rect = input.img.getBoundingClientRect();
|
|
543
|
+
const displayWidth = Math.floor(rect.width);
|
|
544
|
+
const displayHeight = Math.floor(rect.height);
|
|
545
|
+
const grabOffsetX = this.getGrabOffset(
|
|
546
|
+
input.event.clientX - rect.left,
|
|
547
|
+
displayWidth
|
|
548
|
+
);
|
|
549
|
+
const grabOffsetY = this.getGrabOffset(
|
|
550
|
+
input.event.clientY - rect.top,
|
|
551
|
+
displayHeight
|
|
552
|
+
);
|
|
553
|
+
return {
|
|
554
|
+
displayWidth,
|
|
555
|
+
displayHeight,
|
|
556
|
+
grabOffsetX,
|
|
557
|
+
grabOffsetY,
|
|
558
|
+
src: input.img.src,
|
|
559
|
+
persistImage: input.persistImage === true
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Clamps a raw pointer offset into the rendered image bounds.
|
|
564
|
+
*/
|
|
565
|
+
getGrabOffset(rawOffset, displaySize) {
|
|
566
|
+
return this.clampFloor(rawOffset, 0, displaySize);
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Floors a number and clamps it into the provided inclusive range.
|
|
570
|
+
*/
|
|
571
|
+
clampFloor(value, min, max) {
|
|
572
|
+
return Math.min(Math.max(Math.floor(value), min), max);
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Configures the browser drag payload and drag preview image for the current
|
|
576
|
+
* drag operation.
|
|
577
|
+
*/
|
|
578
|
+
applyNativeDragSetup(dataTransfer, img, metrics) {
|
|
579
|
+
if (!dataTransfer) {
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
const { displayWidth, displayHeight, grabOffsetX, grabOffsetY, src } = metrics;
|
|
583
|
+
dataTransfer.setData("text/plain", src);
|
|
584
|
+
dataTransfer.effectAllowed = "copy";
|
|
585
|
+
const previewCanvas = this.getOrCreateDragPreviewCanvas();
|
|
586
|
+
if (previewCanvas.canvas && previewCanvas.context) {
|
|
587
|
+
previewCanvas.canvas.width = displayWidth;
|
|
588
|
+
previewCanvas.canvas.height = displayHeight;
|
|
589
|
+
previewCanvas.canvas.style.width = `${displayWidth}px`;
|
|
590
|
+
previewCanvas.canvas.style.height = `${displayHeight}px`;
|
|
591
|
+
previewCanvas.context.clearRect(0, 0, displayWidth, displayHeight);
|
|
592
|
+
previewCanvas.context.drawImage(img, 0, 0, displayWidth, displayHeight);
|
|
593
|
+
dataTransfer.setDragImage(previewCanvas.canvas, grabOffsetX, grabOffsetY);
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
dataTransfer.setDragImage(img, grabOffsetX, grabOffsetY);
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Creates and reuses the hidden canvas used as the native drag preview.
|
|
600
|
+
* Reuse one canvas as a native drag image so we can control preview size exactly.
|
|
601
|
+
* Browsers may auto-scale element drag ghosts, which shifts perceived cursor alignment.
|
|
602
|
+
* Must be in the DOM - Chrome ignores setDragImage on detached elements and falls back
|
|
603
|
+
* to a scaled ghost. Position it off-screen so it's invisible.
|
|
604
|
+
*/
|
|
605
|
+
getOrCreateDragPreviewCanvas() {
|
|
606
|
+
if (typeof document === "undefined" || !document.body) {
|
|
607
|
+
return {
|
|
608
|
+
canvas: null,
|
|
609
|
+
context: null
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
if (!this.dragPreviewCanvas) {
|
|
613
|
+
this.dragPreviewCanvas = document.createElement("canvas");
|
|
614
|
+
this.dragPreviewCanvas.style.position = "fixed";
|
|
615
|
+
this.dragPreviewCanvas.style.left = HIDDEN_DRAG_PREVIEW_OFFSET_PX;
|
|
616
|
+
this.dragPreviewCanvas.style.top = HIDDEN_DRAG_PREVIEW_OFFSET_PX;
|
|
617
|
+
this.dragPreviewCanvas.style.pointerEvents = "none";
|
|
618
|
+
}
|
|
619
|
+
if (!this.dragPreviewCanvas.isConnected) {
|
|
620
|
+
document.body.appendChild(this.dragPreviewCanvas);
|
|
621
|
+
}
|
|
622
|
+
if (this.dragPreviewCtx === void 0) {
|
|
623
|
+
this.dragPreviewCtx = this.dragPreviewCanvas.getContext("2d");
|
|
624
|
+
}
|
|
625
|
+
return {
|
|
626
|
+
canvas: this.dragPreviewCanvas,
|
|
627
|
+
context: this.dragPreviewCtx ?? null
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Completes the managed drag on `dragend` by ignoring cancelled/internal
|
|
632
|
+
* drops, forwarding valid drops to `dropImage(...)`, and selecting the inserted
|
|
633
|
+
* object when available.
|
|
634
|
+
*/
|
|
635
|
+
async handleManagedDragEnd(metrics, event) {
|
|
636
|
+
const dropInput = {
|
|
637
|
+
x: event.clientX,
|
|
638
|
+
y: event.clientY,
|
|
639
|
+
src: metrics.src,
|
|
640
|
+
imgWidth: metrics.displayWidth,
|
|
641
|
+
imgHeight: metrics.displayHeight,
|
|
642
|
+
grabOffsetX: metrics.grabOffsetX,
|
|
643
|
+
grabOffsetY: metrics.grabOffsetY,
|
|
644
|
+
persistImage: metrics.persistImage
|
|
645
|
+
};
|
|
646
|
+
try {
|
|
647
|
+
if (event.clientX === 0 && event.clientY === 0) {
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
if (this.isInsideIframeViewport(event.clientX, event.clientY)) {
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
const objectId = await this.dropImage(dropInput);
|
|
654
|
+
if (!objectId) {
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
await this.state.setSelectedObjectsIds([objectId]);
|
|
658
|
+
} catch {
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Returns true when the final pointer position is still inside the iframe
|
|
663
|
+
* viewport.
|
|
664
|
+
*/
|
|
665
|
+
isInsideIframeViewport(x, y) {
|
|
666
|
+
return x >= 0 && y >= 0 && x <= window.innerWidth && y <= window.innerHeight;
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
|
|
670
|
+
// src/index.ts
|
|
671
|
+
var kittl = {
|
|
672
|
+
onLoadedCallback: null,
|
|
673
|
+
loaded: false,
|
|
674
|
+
onReady: (cb) => {
|
|
675
|
+
if (kittl.loaded) {
|
|
676
|
+
cb();
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
kittl.onLoadedCallback = cb;
|
|
680
|
+
},
|
|
681
|
+
...bridgeHandlers,
|
|
682
|
+
dragAndDrop: new DragAndDropApi({
|
|
683
|
+
state: bridgeHandlers.state,
|
|
684
|
+
context: bridgeHandlers.context,
|
|
685
|
+
design: bridgeHandlers.design,
|
|
686
|
+
upload: bridgeHandlers.upload
|
|
687
|
+
})
|
|
688
|
+
};
|
|
689
|
+
var initSdk = () => {
|
|
690
|
+
kittl.loaded = true;
|
|
691
|
+
window.addEventListener(
|
|
692
|
+
"message",
|
|
693
|
+
(event) => {
|
|
694
|
+
handleIncomingMessage(event);
|
|
695
|
+
}
|
|
696
|
+
);
|
|
697
|
+
kittl.onLoadedCallback?.();
|
|
698
|
+
sendLoaded();
|
|
699
|
+
};
|
|
700
|
+
if (typeof document !== "undefined" && typeof window !== "undefined") {
|
|
701
|
+
window.kittl = kittl;
|
|
702
|
+
if (document.readyState === "loading") {
|
|
703
|
+
document.addEventListener("DOMContentLoaded", initSdk);
|
|
704
|
+
} else {
|
|
705
|
+
initSdk();
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
})();
|