@oomol-lab/open-flow 0.1.0-beta.89 → 0.1.0-beta.90

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/README.md +29 -0
  2. package/dist/browser/{FlowCanvas-SqPUBqze.js → FlowCanvas-C4iuvVKB.js} +2 -2
  3. package/dist/browser/{exportPng-CLZKdNzR.js → exportPng-DMsB9ku_.js} +4 -4
  4. package/dist/browser/{flowChanges-CnOOPJJR.js → flowChanges-SKYJBr6X.js} +12 -11
  5. package/dist/browser/{flowWorkspace-d9Ef-aRK.js → flowWorkspace-4UHRaHcz.js} +10 -7
  6. package/dist/browser/{markdownContent-D0BBrFcF.js → markdownContent-B75ojl0E.js} +1 -1
  7. package/dist/browser/preview.css +14 -5
  8. package/dist/browser/preview.d.ts +4 -1
  9. package/dist/browser/preview.js +397 -95
  10. package/dist/browser/workbench-contract.d.ts +3 -0
  11. package/dist/browser/workbench.css +14 -5
  12. package/dist/browser/workbench.js +12 -9
  13. package/dist/browser/{wrapLayout-BB_qwy3k.js → wrapLayout-DU5HG1qH.js} +371 -71
  14. package/dist/command/index.js +13 -13
  15. package/dist/common/cache.d.ts +7 -0
  16. package/dist/common/catalogCache.d.ts +3 -7
  17. package/dist/common/control-api-conformance.js +10 -9
  18. package/dist/common/control-api.d.ts +3 -1
  19. package/dist/common/control-api.js +10 -9
  20. package/dist/common/mcp.js +10 -9
  21. package/dist/common/provider-icons.d.ts +35 -0
  22. package/dist/common/provider-icons.js +134 -0
  23. package/dist/render/browser/runtime.css +14 -5
  24. package/dist/render/browser/runtime.js +667 -365
  25. package/dist/render/common/png.d.ts +7 -0
  26. package/dist/render/node/index.d.ts +10 -2
  27. package/dist/render/node/index.js +1284 -37
  28. package/dist/render/node/licenses.md +24 -0
  29. package/package.json +5 -1
  30. package/src/base/common/cache.ts +8 -0
  31. package/src/connector/common/providerIconCache.ts +191 -0
  32. package/src/control/common/api.ts +10 -3
  33. package/src/control/common/catalogCache.ts +19 -11
@@ -1,7 +1,7 @@
1
1
  import { a as __toCommonJS, n as __esmMin, o as __toESM, r as __exportAll, t as __commonJSMin } from "./rolldown-runtime-CEgmsyvS.js";
2
2
  import { _ as readonlyVal, a as isPlainObject$2, c as I18nProvider, d as useTranslate, f as useVal, g as derive, h as compute, i as isBoolean$1, l as useI18n, m as combine, n as pasteVideoLink, o as isString$1, p as attachSetter, r as isArray$2, s as toPlainObject, u as useLang, v as strictEqual, y as val } from "./videoLink-NAWDmBtf.js";
3
3
  import * as m$4 from "react";
4
- import reactExports, { Children, Fragment, Suspense, createContext, createElement, forwardRef, isValidElement, lazy, memo, useCallback, useContext, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo, useReducer, useRef, useState, useSyncExternalStore } from "react";
4
+ import reactExports, { Children, Fragment, Suspense, createContext, createElement, forwardRef, isValidElement, lazy, memo, useCallback, useContext, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo, useReducer, useRef, useState } from "react";
5
5
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
6
6
  import * as ReactDOM from "react-dom";
7
7
  import { createPortal, flushSync } from "react-dom";
@@ -66,6 +66,299 @@ function useIconifyCollectionLoader() {
66
66
  /** Resource selection only; visual styling remains owned by theme.css. */
67
67
  var IconThemeContext = createContext("light");
68
68
  //#endregion
69
+ //#region src/connector/common/providerIconCache.ts
70
+ var defaultMaxAgeMs = 864e5;
71
+ var dataUrl = (entry) => `data:${entry.contentType};base64,${entry.body}`;
72
+ function decodeRecord(value) {
73
+ if (value == null || typeof value !== "object") return;
74
+ const v = value;
75
+ if (v.formatVersion !== 1 || typeof v.sourceUrl !== "string" || !(v.spriteVersion === null || typeof v.spriteVersion === "string") || typeof v.contentType !== "string" || !/^image\/[\w.+-]+$/i.test(v.contentType) || typeof v.body !== "string" || !v.body || v.body.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(v.body) || !(v.etag === null || typeof v.etag === "string") || !Number.isFinite(v.validatedAt) || v.validatedAt < 0 || !(v.maxAgeMs === null || Number.isFinite(v.maxAgeMs) && v.maxAgeMs >= 0)) return;
76
+ return v;
77
+ }
78
+ function encodeBytes(bytes) {
79
+ let text = "";
80
+ for (let index = 0; index < bytes.length; index += 8192) text += String.fromCharCode(...bytes.subarray(index, index + 8192));
81
+ return btoa(text);
82
+ }
83
+ function freshness(response, immutable, previous) {
84
+ const control = response.headers.get("cache-control") ?? "";
85
+ if (/\bno-cache\b/i.test(control)) return 0;
86
+ if (immutable) return null;
87
+ const maxAge = /(?:^|,)\s*max-age\s*=\s*"?(\d+)/i.exec(control);
88
+ if (maxAge) return Math.max(0, Number(maxAge[1]) * 1e3 - (Number(response.headers.get("age")) || 0) * 1e3);
89
+ const expires = Date.parse(response.headers.get("expires") ?? "");
90
+ if (Number.isFinite(expires)) return Math.max(0, expires - Date.parse(response.headers.get("date") ?? (/* @__PURE__ */ new Date()).toUTCString()));
91
+ return previous?.maxAgeMs ?? defaultMaxAgeMs;
92
+ }
93
+ /** Complete image responses in bounded, identity-checked slots; no catalog entities live here. */
94
+ var ProviderIconCache = class {
95
+ #memory = /* @__PURE__ */ new Map();
96
+ #pending = /* @__PURE__ */ new Map();
97
+ #targets = /* @__PURE__ */ new Map();
98
+ #writes = /* @__PURE__ */ new Map();
99
+ constructor(options) {
100
+ this.options = options;
101
+ if (!options.scope.trim()) throw new TypeError("Provider icon cache scope must be nonempty.");
102
+ }
103
+ get(request, options = {}) {
104
+ if (request.url.startsWith("data:image/")) return Promise.resolve(request.url);
105
+ let protocol;
106
+ try {
107
+ protocol = new URL(request.url).protocol;
108
+ } catch (error) {
109
+ return Promise.reject(error);
110
+ }
111
+ if (!["http:", "https:"].includes(protocol)) return Promise.reject(/* @__PURE__ */ new TypeError("Unsupported Provider icon URL."));
112
+ const slot = JSON.stringify([
113
+ "open-flow:provider-icons",
114
+ 1,
115
+ this.options.scope,
116
+ request.kind,
117
+ request.kind === "sprite" ? request.theme : request.providerId
118
+ ]);
119
+ const version = request.kind === "sprite" ? request.version : null;
120
+ const identity = JSON.stringify([request.url, version]);
121
+ const offline = options.allowNetwork === false || this.options.mode === "offline";
122
+ const key = JSON.stringify([
123
+ slot,
124
+ identity,
125
+ offline
126
+ ]);
127
+ const pending = this.#pending.get(key);
128
+ if (pending) return pending;
129
+ if (!offline) this.#targets.set(slot, identity);
130
+ const promise = this.#load(slot, identity, request.url, version, offline).finally(() => this.#pending.delete(key));
131
+ this.#pending.set(key, promise);
132
+ return promise;
133
+ }
134
+ async #read(slot, url, version) {
135
+ const memory = this.#memory.get(slot);
136
+ if (this.#memory.has(slot) && (!memory || memory.sourceUrl === url && memory.spriteVersion === version)) return memory;
137
+ if (!this.options.delegate) return this.#memory.get(slot);
138
+ try {
139
+ const stored = await this.options.delegate.get(slot);
140
+ if (stored === void 0) return this.#memory.get(slot);
141
+ const entry = decodeRecord(stored);
142
+ if (entry) {
143
+ this.#memory.set(slot, entry);
144
+ return entry;
145
+ }
146
+ this.#memory.delete(slot);
147
+ await this.options.delegate.delete(slot);
148
+ } catch {
149
+ return this.#memory.get(slot);
150
+ }
151
+ }
152
+ async #store(slot, identity, entry) {
153
+ const operation = (this.#writes.get(slot) ?? Promise.resolve()).then(async () => {
154
+ if (this.#targets.get(slot) !== identity) return;
155
+ this.#memory.set(slot, entry);
156
+ if (!this.options.delegate) return;
157
+ try {
158
+ if (entry) await this.options.delegate.set(slot, { ...entry });
159
+ else await this.options.delegate.delete(slot);
160
+ } catch {}
161
+ });
162
+ this.#writes.set(slot, operation);
163
+ try {
164
+ await operation;
165
+ } finally {
166
+ if (this.#writes.get(slot) === operation) this.#writes.delete(slot);
167
+ }
168
+ }
169
+ async #load(slot, identity, url, version, offline) {
170
+ const stored = await this.#read(slot, url, version);
171
+ const cached = stored?.sourceUrl === url && stored.spriteVersion === version ? stored : void 0;
172
+ const age = cached == null ? Infinity : Date.now() - cached.validatedAt;
173
+ if (cached && (offline || this.options.mode !== "refresh" && age >= 0 && (cached.maxAgeMs === null || age < cached.maxAgeMs))) return dataUrl(cached);
174
+ if (offline) throw new Error(`Provider icon is not cached: ${url}`);
175
+ const response = await (this.options.fetch ?? globalThis.fetch)(url, {
176
+ credentials: "omit",
177
+ referrerPolicy: "no-referrer",
178
+ signal: AbortSignal.timeout(15e3),
179
+ headers: cached?.etag ? { "if-none-match": cached.etag } : {}
180
+ });
181
+ if (response.status !== 304 && !response.ok) throw new Error(`Could not load Provider icon: ${response.status}`);
182
+ if (response.status === 304 && !cached) throw new Error("Provider icon returned 304 without a cached image.");
183
+ const contentType = response.status === 304 ? cached.contentType : response.headers.get("content-type")?.split(";")[0]?.trim().toLowerCase();
184
+ if (!contentType || !/^image\/[\w.+-]+$/i.test(contentType)) throw new Error("Provider icon response is not an image.");
185
+ const body = response.status === 304 ? cached.body : encodeBytes(new Uint8Array(await response.arrayBuffer()));
186
+ if (!body) throw new Error("Provider icon response is empty.");
187
+ const entry = {
188
+ formatVersion: 1,
189
+ sourceUrl: url,
190
+ spriteVersion: version,
191
+ contentType,
192
+ body,
193
+ etag: response.headers.get("etag") ?? (response.status === 304 ? cached.etag : null),
194
+ validatedAt: Date.now(),
195
+ maxAgeMs: freshness(response, version !== null, response.status === 304 ? cached : void 0)
196
+ };
197
+ await this.#store(slot, identity, /\bno-store\b/i.test(response.headers.get("cache-control") ?? "") ? void 0 : entry);
198
+ return dataUrl(entry);
199
+ }
200
+ };
201
+ //#endregion
202
+ //#region src/ui/browser/icons/providerIconResource.ts
203
+ var ProviderIconCacheContext = createContext(void 0);
204
+ /** IDB holds the same opaque JSON records as a host's disk delegate. */
205
+ function indexedDbIconDelegate() {
206
+ let database;
207
+ function open() {
208
+ database ??= new Promise((resolve, reject) => {
209
+ const request = indexedDB.open("open-flow-provider-icons", 1);
210
+ request.addEventListener("upgradeneeded", () => request.result.createObjectStore("icons"), { once: true });
211
+ request.addEventListener("error", () => reject(request.error), { once: true });
212
+ request.addEventListener("blocked", () => reject(/* @__PURE__ */ new Error("Provider icon cache is blocked.")), { once: true });
213
+ request.addEventListener("success", () => {
214
+ const db = request.result;
215
+ db.addEventListener("versionchange", () => {
216
+ db.close();
217
+ database = void 0;
218
+ }, { once: true });
219
+ resolve(db);
220
+ }, { once: true });
221
+ }).catch((error) => {
222
+ database = void 0;
223
+ throw error;
224
+ });
225
+ return database;
226
+ }
227
+ async function transaction(mode, operation) {
228
+ const db = await open();
229
+ return new Promise((resolve, reject) => {
230
+ const tx = db.transaction("icons", mode);
231
+ const request = operation(tx.objectStore("icons"));
232
+ tx.addEventListener("complete", () => resolve(request.result), { once: true });
233
+ tx.addEventListener("abort", () => reject(tx.error ?? request.error), { once: true });
234
+ tx.addEventListener("error", () => reject(tx.error ?? request.error), { once: true });
235
+ });
236
+ }
237
+ return {
238
+ get: (key) => transaction("readonly", (store) => store.get(key)),
239
+ set: async (key, value) => {
240
+ await transaction("readwrite", (store) => store.put(value, key));
241
+ },
242
+ delete: async (key) => {
243
+ await transaction("readwrite", (store) => store.delete(key));
244
+ }
245
+ };
246
+ }
247
+ var browserCache;
248
+ function defaultBrowserIconCache() {
249
+ return browserCache ??= new ProviderIconCache({
250
+ scope: "browser",
251
+ delegate: indexedDbIconDelegate()
252
+ });
253
+ }
254
+ function useProviderIconCache() {
255
+ return useContext(ProviderIconCacheContext) ?? defaultBrowserIconCache();
256
+ }
257
+ var loading = { status: "loading" };
258
+ function createIconResource(cache, request, ready) {
259
+ const state = val(ready ?? loading);
260
+ let pending;
261
+ return {
262
+ state,
263
+ refresh() {
264
+ if (pending) return pending;
265
+ const previous = state.value.status === "loaded" ? state.value : void 0;
266
+ if (previous) state.set({
267
+ ...previous,
268
+ refreshing: true
269
+ });
270
+ else if (state.value.status === "failed") state.set(loading);
271
+ pending = (async () => {
272
+ let image;
273
+ try {
274
+ const url = await cache.get(request);
275
+ if (previous?.url === url) {
276
+ state.set({
277
+ ...previous,
278
+ refreshing: false
279
+ });
280
+ return;
281
+ }
282
+ image = new Image();
283
+ image.src = url;
284
+ await image.decode();
285
+ state.set({
286
+ status: "loaded",
287
+ url,
288
+ image
289
+ });
290
+ } catch {
291
+ if (image) image.src = "";
292
+ state.set(previous ? {
293
+ ...previous,
294
+ refreshing: false
295
+ } : { status: "failed" });
296
+ }
297
+ })().finally(() => {
298
+ pending = void 0;
299
+ });
300
+ return pending;
301
+ }
302
+ };
303
+ }
304
+ var resources$1 = /* @__PURE__ */ new WeakMap();
305
+ /** Components read ready state synchronously; only the shared owner fetches and decodes. */
306
+ function useProviderIconResource(request, crop) {
307
+ const cache = useProviderIconCache();
308
+ const slot = JSON.stringify([request.kind, request.kind === "sprite" ? request.theme : request.providerId]);
309
+ const identity = JSON.stringify([
310
+ slot,
311
+ request.url,
312
+ request.kind === "sprite" ? request.version : null
313
+ ]);
314
+ const previous = useRef();
315
+ const resource = useMemo(() => {
316
+ let slots = resources$1.get(cache);
317
+ if (!slots) {
318
+ slots = /* @__PURE__ */ new Map();
319
+ resources$1.set(cache, slots);
320
+ }
321
+ let current = slots.get(slot);
322
+ if (current?.identity !== identity) {
323
+ const value = previous.current?.identity === identity ? previous.current.resource.state.value : void 0;
324
+ current = {
325
+ identity,
326
+ resource: createIconResource(cache, request, value?.status === "loaded" ? value : void 0)
327
+ };
328
+ slots.set(slot, current);
329
+ }
330
+ previous.current = current;
331
+ return current.resource;
332
+ }, [cache, identity]);
333
+ const state = useVal(resource.state);
334
+ useEffect(() => {
335
+ resource.refresh();
336
+ }, [resource]);
337
+ return useMemo(() => {
338
+ if (state.status !== "loaded" || !crop) return state;
339
+ const canvas = document.createElement("canvas");
340
+ try {
341
+ canvas.width = canvas.height = crop.size;
342
+ const context = canvas.getContext("2d");
343
+ if (!context) throw new Error("Could not crop the Provider icon.");
344
+ context.drawImage(state.image, crop.x, crop.y, crop.size, crop.size, 0, 0, crop.size, crop.size);
345
+ return {
346
+ ...state,
347
+ url: canvas.toDataURL("image/png")
348
+ };
349
+ } catch {
350
+ return { status: "failed" };
351
+ } finally {
352
+ canvas.width = canvas.height = 0;
353
+ }
354
+ }, [
355
+ state,
356
+ crop?.x,
357
+ crop?.y,
358
+ crop?.size
359
+ ]);
360
+ }
361
+ //#endregion
69
362
  //#region ../../node_modules/.bun/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
70
363
  function r$1(e) {
71
364
  var t, f, n = "";
@@ -33742,9 +34035,10 @@ var Zap = createLucideIcon("Zap", [["path", {
33742
34035
  key: "1xq2db"
33743
34036
  }]]);
33744
34037
  var ContentIcon_module_default = {
33745
- img: "ContentIcon_img_4e5d2",
33746
- initials: "ContentIcon_initials_4e5d2",
33747
- sprite: "ContentIcon_sprite_4e5d2"
34038
+ img: "ContentIcon_img_e2c99",
34039
+ pending: "ContentIcon_pending_e2c99",
34040
+ initials: "ContentIcon_initials_e2c99",
34041
+ sprite: "ContentIcon_sprite_e2c99"
33748
34042
  };
33749
34043
  //#endregion
33750
34044
  //#region src/connector/common/providerIconSprite.ts
@@ -33988,60 +34282,8 @@ function generateSvgDataUri(width, height, rawIcon, color) {
33988
34282
  return `data:image/svg+xml;utf8,${encodeSvgForCss(svg)}`;
33989
34283
  }
33990
34284
  //#endregion
33991
- //#region src/ui/browser/icons/spriteResource.ts
33992
- function createResource(url) {
33993
- let status = "loading";
33994
- let started = false;
33995
- const listeners = /* @__PURE__ */ new Set();
33996
- return {
33997
- getSnapshot: () => status,
33998
- subscribe(listener) {
33999
- listeners.add(listener);
34000
- if (!started) {
34001
- started = true;
34002
- const finish = (next) => {
34003
- status = next;
34004
- for (const notify of listeners) notify();
34005
- };
34006
- if (typeof Image == "undefined") finish("failed");
34007
- else {
34008
- const image = new Image();
34009
- const settle = (next) => {
34010
- image.removeEventListener("load", loaded);
34011
- image.removeEventListener("error", failed);
34012
- finish(next);
34013
- };
34014
- const loaded = () => settle("loaded");
34015
- const failed = () => settle("failed");
34016
- image.addEventListener("load", loaded, { once: true });
34017
- image.addEventListener("error", failed, { once: true });
34018
- image.referrerPolicy = "no-referrer";
34019
- image.src = url;
34020
- }
34021
- }
34022
- return () => {
34023
- listeners.delete(listener);
34024
- };
34025
- }
34026
- };
34027
- }
34028
- var resources$1 = /* @__PURE__ */ new Map();
34029
- function spriteResource(url) {
34030
- let resource = resources$1.get(url);
34031
- if (!resource) {
34032
- resource = createResource(url);
34033
- resources$1.set(url, resource);
34034
- }
34035
- return resource;
34036
- }
34037
- var serverSnapshot = () => "loading";
34038
- function useSpriteStatus(url) {
34039
- const resource = spriteResource(url);
34040
- return useSyncExternalStore(resource.subscribe, resource.getSnapshot, serverSnapshot);
34041
- }
34042
- //#endregion
34043
34285
  //#region src/ui/browser/icons/ContentIcon.tsx
34044
- /** Standalone image capture uses individual, cross-origin-readable images instead of sprites. */
34286
+ /** Standalone image capture crops sprites and uses cross-origin-readable images. */
34045
34287
  var IconImageCrossOriginContext = createContext(void 0);
34046
34288
  /** A bundled, theme-aware placeholder that does not load a remote icon. */
34047
34289
  var placeholderIcon = "data:application/vnd.open-flow.placeholder-icon,";
@@ -34053,18 +34295,19 @@ var ContentIcon = ({ src, className, fallback = null, loading = "lazy", decoding
34053
34295
  const result = useMemo(() => parseIconifyIcon(source), [source]);
34054
34296
  const [error, setError] = useState();
34055
34297
  const onError = () => setError(source);
34056
- if (sprite) return crossOrigin ? /* @__PURE__ */ jsx(ContentIcon, {
34298
+ if (sprite) return /* @__PURE__ */ jsx(SpriteIcon, {
34057
34299
  className,
34058
34300
  fallback,
34059
34301
  loading,
34060
34302
  decoding,
34061
- src: sprite.fallback
34062
- }) : /* @__PURE__ */ jsx(SpriteIcon, {
34303
+ sprite
34304
+ });
34305
+ if (image?.providerId) return /* @__PURE__ */ jsx(ProviderImage, {
34063
34306
  className,
34064
34307
  fallback,
34065
34308
  loading,
34066
34309
  decoding,
34067
- sprite
34310
+ image
34068
34311
  });
34069
34312
  if (source === "data:application/vnd.open-flow.placeholder-icon,") return /* @__PURE__ */ jsx("i", {
34070
34313
  "aria-hidden": "true",
@@ -34107,10 +34350,11 @@ var ContentIcon = ({ src, className, fallback = null, loading = "lazy", decoding
34107
34350
  };
34108
34351
  var initialsIconPrefix = "data:application/vnd.open-flow.initials,";
34109
34352
  var imageIconPrefix = "data:application/vnd.open-flow.image-icon+json,";
34110
- function imageIcon(source, fallback) {
34353
+ function imageIcon(source, fallback, providerId) {
34111
34354
  return `${imageIconPrefix}${encodeURIComponent(JSON.stringify({
34112
34355
  fallback,
34113
- source
34356
+ source,
34357
+ ...providerId == null ? {} : { providerId }
34114
34358
  }))}`;
34115
34359
  }
34116
34360
  function parseImageIcon(src) {
@@ -34119,7 +34363,8 @@ function parseImageIcon(src) {
34119
34363
  const value = JSON.parse(decodeURIComponent(src.slice(47)));
34120
34364
  if (typeof value.fallback == "string" && typeof value.source == "string") return {
34121
34365
  fallback: value.fallback,
34122
- source: value.source
34366
+ source: value.source,
34367
+ ...typeof value.providerId === "string" ? { providerId: value.providerId } : {}
34123
34368
  };
34124
34369
  } catch {
34125
34370
  return;
@@ -34159,19 +34404,39 @@ function parseSpriteIcon(src) {
34159
34404
  }
34160
34405
  function SpriteIcon({ sprite, ...props }) {
34161
34406
  const theme = useContext(IconThemeContext);
34407
+ const capture = useContext(IconImageCrossOriginContext) != null;
34162
34408
  const { iconSprite: m, iconSpritePosition: p } = sprite;
34163
- const url = theme == "dark" ? m.darkUrl : m.lightUrl;
34164
- const status = useSpriteStatus(url);
34165
- if (status == "failed") return /* @__PURE__ */ jsx(ContentIcon, {
34409
+ const resource = useProviderIconResource({
34410
+ kind: "sprite",
34411
+ theme,
34412
+ url: theme == "dark" ? m.darkUrl : m.lightUrl,
34413
+ version: m.version
34414
+ }, capture ? {
34415
+ x: p.x,
34416
+ y: p.y,
34417
+ size: m.iconSize
34418
+ } : void 0);
34419
+ if (resource.status == "failed") return /* @__PURE__ */ jsx(ContentIcon, {
34166
34420
  ...props,
34167
34421
  src: sprite.fallback
34168
34422
  });
34423
+ if (resource.status === "loading") return /* @__PURE__ */ jsx(PendingIcon, {
34424
+ className: props.className,
34425
+ fallback: props.fallback
34426
+ });
34427
+ if (capture) return /* @__PURE__ */ jsx("img", {
34428
+ alt: "",
34429
+ className: clsx(ContentIcon_module_default.img, props.className),
34430
+ "data-render-pending": resource.refreshing || void 0,
34431
+ src: resource.url,
34432
+ decoding: "sync"
34433
+ });
34169
34434
  return /* @__PURE__ */ jsx("span", {
34170
34435
  "aria-hidden": "true",
34171
- "data-render-pending": status == "loading" || void 0,
34436
+ "data-render-pending": resource.refreshing || void 0,
34172
34437
  className: clsx(ContentIcon_module_default.sprite, props.className),
34173
- children: status == "loaded" && /* @__PURE__ */ jsx("span", { style: {
34174
- backgroundImage: `url(${JSON.stringify(url)})`,
34438
+ children: /* @__PURE__ */ jsx("span", { style: {
34439
+ backgroundImage: `url(${JSON.stringify(resource.url)})`,
34175
34440
  width: `${m.width / m.iconSize * 100}%`,
34176
34441
  height: `${m.height / m.iconSize * 100}%`,
34177
34442
  left: `${-p.x / m.iconSize * 100}%`,
@@ -34179,6 +34444,40 @@ function SpriteIcon({ sprite, ...props }) {
34179
34444
  } })
34180
34445
  });
34181
34446
  }
34447
+ function ProviderImage({ image, ...props }) {
34448
+ const resource = useProviderIconResource({
34449
+ kind: "image",
34450
+ providerId: image.providerId,
34451
+ url: image.source
34452
+ });
34453
+ if (resource.status === "failed") return /* @__PURE__ */ jsx(ContentIcon, {
34454
+ ...props,
34455
+ src: image.fallback
34456
+ });
34457
+ if (resource.status === "loading") return /* @__PURE__ */ jsx(PendingIcon, {
34458
+ className: props.className,
34459
+ fallback: props.fallback
34460
+ });
34461
+ return /* @__PURE__ */ jsx("img", {
34462
+ alt: "",
34463
+ className: clsx(ContentIcon_module_default.img, props.className),
34464
+ "data-render-pending": resource.refreshing || void 0,
34465
+ src: resource.url,
34466
+ loading: props.loading,
34467
+ decoding: props.decoding
34468
+ });
34469
+ }
34470
+ function PendingIcon({ className, fallback }) {
34471
+ return /* @__PURE__ */ jsx("span", {
34472
+ "aria-hidden": "true",
34473
+ className: clsx(ContentIcon_module_default.pending, className),
34474
+ "data-render-pending": true,
34475
+ children: fallback ?? /* @__PURE__ */ jsx(ContentIcon, {
34476
+ src: "data:application/vnd.open-flow.placeholder-icon,",
34477
+ className
34478
+ })
34479
+ });
34480
+ }
34182
34481
  var handle_module_default = {
34183
34482
  wrapper: "handle_wrapper_c0108",
34184
34483
  handle: "handle_handle_c0108",
@@ -65785,7 +66084,7 @@ function provider(value, metadata) {
65785
66084
  //#region src/workbench/browser/runtime/providerIcon.ts
65786
66085
  function providerIcon(provider) {
65787
66086
  const source = provider.icon?.trim();
65788
- const original = source ? imageIcon(source, placeholderIcon) : placeholderIcon;
66087
+ const original = source ? imageIcon(source, placeholderIcon, provider.serviceId) : placeholderIcon;
65789
66088
  const appearance = providerIconAppearance(provider.iconSprite, provider.iconSpritePosition);
65790
66089
  return appearance?.iconSprite && appearance.iconSpritePosition ? spriteIcon({
65791
66090
  iconSprite: appearance.iconSprite,
@@ -120415,36 +120714,39 @@ function RevisionCanvas({ draft, presentation, theme, interactiveMode$, defaultI
120415
120714
  //#endregion
120416
120715
  //#region src/workbench/browser/runtime/openFlowPreview.tsx
120417
120716
  /** Offline workflow preview. Use a React key to start a new layout/selection session. */
120418
- function OpenFlowPreview({ draft, presentation, language, theme, defaultInteractiveMode, defaultDirection, defaultMiniMapExpanded, defaultInspectorOpen, defaultSelectedNodeIds, nodesDraggable = true, dottedBackground = true, layoutMotion = true, label, className }) {
120717
+ function OpenFlowPreview({ iconCache, draft, presentation, language, theme, defaultInteractiveMode, defaultDirection, defaultMiniMapExpanded, defaultInspectorOpen, defaultSelectedNodeIds, nodesDraggable = true, dottedBackground = true, layoutMotion = true, label, className }) {
120419
120718
  const [i18n] = useState(() => createI18n(language));
120420
120719
  useEffect(() => {
120421
120720
  if (i18n.lang !== language) i18n.switchLang(language);
120422
120721
  }, [i18n, language]);
120423
120722
  useEffect(() => () => i18n.dispose(), [i18n]);
120424
- return /* @__PURE__ */ jsx(IconThemeContext.Provider, {
120425
- value: theme,
120426
- children: /* @__PURE__ */ jsx(IconifyProvider, { children: /* @__PURE__ */ jsx(I18nProvider, {
120427
- i18n,
120428
- children: /* @__PURE__ */ jsx("div", {
120429
- className: cn$1("open-flow-theme open-flow-workbench open-flow-preview", className),
120430
- "data-theme": theme,
120431
- children: /* @__PURE__ */ jsx(RevisionCanvas, {
120432
- draft,
120433
- presentation,
120434
- theme,
120435
- defaultInteractiveMode,
120436
- defaultDirection,
120437
- defaultMiniMapExpanded,
120438
- defaultInspectorOpen,
120439
- defaultSelectedNodeIds,
120440
- nodesDraggable,
120441
- layoutMotion,
120442
- label,
120443
- background: dottedBackground ? "dotted" : "none",
120444
- showHeader: false
120723
+ return /* @__PURE__ */ jsx(ProviderIconCacheContext.Provider, {
120724
+ value: iconCache,
120725
+ children: /* @__PURE__ */ jsx(IconThemeContext.Provider, {
120726
+ value: theme,
120727
+ children: /* @__PURE__ */ jsx(IconifyProvider, { children: /* @__PURE__ */ jsx(I18nProvider, {
120728
+ i18n,
120729
+ children: /* @__PURE__ */ jsx("div", {
120730
+ className: cn$1("open-flow-theme open-flow-workbench open-flow-preview", className),
120731
+ "data-theme": theme,
120732
+ children: /* @__PURE__ */ jsx(RevisionCanvas, {
120733
+ draft,
120734
+ presentation,
120735
+ theme,
120736
+ defaultInteractiveMode,
120737
+ defaultDirection,
120738
+ defaultMiniMapExpanded,
120739
+ defaultInspectorOpen,
120740
+ defaultSelectedNodeIds,
120741
+ nodesDraggable,
120742
+ layoutMotion,
120743
+ label,
120744
+ background: dottedBackground ? "dotted" : "none",
120745
+ showHeader: false
120746
+ })
120445
120747
  })
120446
- })
120447
- }) })
120748
+ }) })
120749
+ })
120448
120750
  });
120449
120751
  }
120450
120752
  //#endregion
@@ -1,4 +1,5 @@
1
1
  import type { ReactNode } from 'react';
2
+ import type { ProviderIconLoader } from '../common/provider-icons.js';
2
3
  import type { WorkflowBundleImportResult } from '../common/control-api.js';
3
4
  import type { FlowCatalogEvent, FlowChangeEvent } from './flow-notifications.js';
4
5
  import type { WorkflowBundle } from '../common/flow-workflowBundle.js';
@@ -22,6 +23,8 @@ export interface WorkbenchActor {
22
23
  readonly avatarUrl?: string;
23
24
  }
24
25
  export interface WorkbenchHost {
26
+ /** Optional shared Provider artwork cache; defaults to browser IndexedDB. */
27
+ readonly iconCache?: ProviderIconLoader;
25
28
  /** Optional deployment-owned identity lookup. Return null for an unknown actor. */
26
29
  readonly resolveActor?: (actorId: string, signal: AbortSignal) => Promise<WorkbenchActor | null>;
27
30
  /** Cache deployment identity. The session id must be stable and non-secret. */
@@ -1,4 +1,4 @@
1
- @charset "UTF-8";.ContentIcon_img_4e5d2 {
1
+ @charset "UTF-8";.ContentIcon_img_e2c99 {
2
2
  width: 1em;
3
3
  height: 1em;
4
4
  vertical-align: middle;
@@ -6,7 +6,16 @@
6
6
  object-fit: contain;
7
7
  }
8
8
 
9
- .ContentIcon_initials_4e5d2 {
9
+ .ContentIcon_pending_e2c99 {
10
+ display: inline-flex;
11
+ width: 1em;
12
+ height: 1em;
13
+ align-items: center;
14
+ justify-content: center;
15
+ vertical-align: middle;
16
+ }
17
+
18
+ .ContentIcon_initials_e2c99 {
10
19
  display: inline-flex;
11
20
  width: 1em;
12
21
  height: 1em;
@@ -19,13 +28,13 @@
19
28
  vertical-align: middle;
20
29
  pointer-events: none;
21
30
  }
22
- .ContentIcon_initials_4e5d2 > span {
31
+ .ContentIcon_initials_e2c99 > span {
23
32
  font-size: 0.42em;
24
33
  font-weight: 400;
25
34
  line-height: 1;
26
35
  }
27
36
 
28
- .ContentIcon_sprite_4e5d2 {
37
+ .ContentIcon_sprite_e2c99 {
29
38
  display: inline-block;
30
39
  position: relative;
31
40
  overflow: hidden;
@@ -35,7 +44,7 @@
35
44
  vertical-align: middle;
36
45
  pointer-events: none;
37
46
  }
38
- .ContentIcon_sprite_4e5d2 > span {
47
+ .ContentIcon_sprite_e2c99 > span {
39
48
  position: absolute;
40
49
  background-size: 100% 100%;
41
50
  background-repeat: no-repeat;