@tutti-os/workbench-surface 0.0.224 → 0.0.225

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3820,17 +3820,40 @@ function renderGenieScanlines(context, viewportWidth, viewportHeight, frame) {
3820
3820
  }
3821
3821
 
3822
3822
  // src/react/genieTextureCapture.ts
3823
+ var readableStylesheetTextByDocument = /* @__PURE__ */ new WeakMap();
3824
+ function hasSameStylesheetFingerprints(left, right) {
3825
+ return left.length === right.length && left.every((fingerprint, index) => {
3826
+ const candidate = right[index];
3827
+ return candidate?.stylesheet === fingerprint.stylesheet && candidate.disabled === fingerprint.disabled && candidate.ruleCount === fingerprint.ruleCount && candidate.ownerText === fingerprint.ownerText;
3828
+ });
3829
+ }
3823
3830
  function collectReadableStylesheetText(document2) {
3824
- const rules = [];
3831
+ const readableStylesheets = [];
3825
3832
  for (const stylesheet of Array.from(document2.styleSheets)) {
3826
3833
  try {
3827
- for (const rule of Array.from(stylesheet.cssRules)) {
3828
- rules.push(rule.cssText);
3829
- }
3834
+ const rules = stylesheet.cssRules;
3835
+ readableStylesheets.push({
3836
+ fingerprint: {
3837
+ disabled: stylesheet.disabled,
3838
+ ownerText: stylesheet.ownerNode instanceof HTMLStyleElement ? stylesheet.ownerNode.textContent : null,
3839
+ ruleCount: rules.length,
3840
+ stylesheet
3841
+ },
3842
+ rules
3843
+ });
3830
3844
  } catch {
3831
3845
  }
3832
3846
  }
3833
- return rules.join("\n");
3847
+ const fingerprints = readableStylesheets.map(
3848
+ ({ fingerprint }) => fingerprint
3849
+ );
3850
+ const cached = readableStylesheetTextByDocument.get(document2);
3851
+ if (cached && hasSameStylesheetFingerprints(cached.fingerprints, fingerprints)) {
3852
+ return cached.text;
3853
+ }
3854
+ const text = readableStylesheets.flatMap(({ rules }) => Array.from(rules, (rule) => rule.cssText)).join("\n");
3855
+ readableStylesheetTextByDocument.set(document2, { fingerprints, text });
3856
+ return text;
3834
3857
  }
3835
3858
  function collectImageClones(element) {
3836
3859
  return Array.from(element.querySelectorAll("img")).map((image) => {
@@ -3838,6 +3861,8 @@ function collectImageClones(element) {
3838
3861
  return {
3839
3862
  displayHeight: rect.height,
3840
3863
  displayWidth: rect.width,
3864
+ naturalHeight: image.naturalHeight,
3865
+ naturalWidth: image.naturalWidth,
3841
3866
  url: image.currentSrc || image.src || image.getAttribute("src") || null
3842
3867
  };
3843
3868
  });
@@ -3936,10 +3961,12 @@ var dockPreviewMaxWidth = 260;
3936
3961
  var dockPreviewMaxHeight = 170;
3937
3962
  var dockPreviewImageCacheMaxEntries = 96;
3938
3963
  var inlineImageResourceCacheMaxEntries = 160;
3964
+ var inlineImageResizeTargetCacheMaxEntries = 4;
3939
3965
  var dockAnchorFallbackSizePx = 43.2;
3940
3966
  var genieInlineImageMaxDevicePixelRatio = 2;
3941
3967
  var dockPreviewImageByNodeID = /* @__PURE__ */ new Map();
3942
3968
  var inlineImageResourceByUrl = /* @__PURE__ */ new Map();
3969
+ var resizedInlineImageResourceByUrl = /* @__PURE__ */ new Map();
3943
3970
  function resolveWorkbenchCaptureElement(windowElement) {
3944
3971
  return windowElement.querySelector(
3945
3972
  '[data-workbench-window-capture="true"]'
@@ -4080,7 +4107,8 @@ async function inlineCloneImageResources({
4080
4107
  return;
4081
4108
  }
4082
4109
  const inlineImageUrl = await readInlineImageResource(imageUrl) ?? imageUrl;
4083
- const resizedImageUrl = await resizeInlineImageResourceForGenieTexture(
4110
+ const resizedImageUrl = await readResizedInlineImageResource(
4111
+ imageUrl,
4084
4112
  inlineImageUrl,
4085
4113
  imageInfo
4086
4114
  );
@@ -4114,11 +4142,7 @@ function resolveGenieInlineImageTargetSize({
4114
4142
  width: Math.max(1, Math.ceil(displayWidth * scale))
4115
4143
  };
4116
4144
  }
4117
- async function resizeInlineImageResourceForGenieTexture(imageUrl, imageInfo) {
4118
- const targetSize = resolveGenieInlineImageTargetSize(imageInfo);
4119
- if (!targetSize) {
4120
- return null;
4121
- }
4145
+ async function resizeInlineImageResourceForGenieTexture(imageUrl, targetSize) {
4122
4146
  const image = new Image();
4123
4147
  image.src = imageUrl;
4124
4148
  try {
@@ -4143,6 +4167,50 @@ async function resizeInlineImageResourceForGenieTexture(imageUrl, imageInfo) {
4143
4167
  return null;
4144
4168
  }
4145
4169
  }
4170
+ function readResizedInlineImageResource(sourceImageUrl, inlineImageUrl, imageInfo) {
4171
+ const targetSize = resolveGenieInlineImageTargetSize(imageInfo);
4172
+ if (!targetSize) {
4173
+ return Promise.resolve(null);
4174
+ }
4175
+ if (imageInfo.naturalWidth > 0 && imageInfo.naturalHeight > 0 && imageInfo.naturalWidth <= targetSize.width && imageInfo.naturalHeight <= targetSize.height) {
4176
+ return Promise.resolve(null);
4177
+ }
4178
+ let resizedByTarget = resizedInlineImageResourceByUrl.get(sourceImageUrl);
4179
+ if (resizedByTarget) {
4180
+ resizedInlineImageResourceByUrl.delete(sourceImageUrl);
4181
+ resizedInlineImageResourceByUrl.set(sourceImageUrl, resizedByTarget);
4182
+ } else {
4183
+ resizedByTarget = /* @__PURE__ */ new Map();
4184
+ resizedInlineImageResourceByUrl.set(sourceImageUrl, resizedByTarget);
4185
+ }
4186
+ const targetKey = `${targetSize.width}x${targetSize.height}`;
4187
+ const cached = resizedByTarget.get(targetKey);
4188
+ if (cached) {
4189
+ resizedByTarget.delete(targetKey);
4190
+ resizedByTarget.set(targetKey, cached);
4191
+ return cached;
4192
+ }
4193
+ const promise = resizeInlineImageResourceForGenieTexture(
4194
+ inlineImageUrl,
4195
+ targetSize
4196
+ );
4197
+ resizedByTarget.set(targetKey, promise);
4198
+ while (resizedByTarget.size > inlineImageResizeTargetCacheMaxEntries) {
4199
+ const oldestTarget = resizedByTarget.keys().next().value;
4200
+ if (typeof oldestTarget !== "string") {
4201
+ break;
4202
+ }
4203
+ resizedByTarget.delete(oldestTarget);
4204
+ }
4205
+ while (resizedInlineImageResourceByUrl.size > inlineImageResourceCacheMaxEntries) {
4206
+ const oldestImageUrl = resizedInlineImageResourceByUrl.keys().next().value;
4207
+ if (typeof oldestImageUrl !== "string") {
4208
+ break;
4209
+ }
4210
+ resizedInlineImageResourceByUrl.delete(oldestImageUrl);
4211
+ }
4212
+ return promise;
4213
+ }
4146
4214
  function prepareRenderedGeniePreviewCloneForTexture(clone, textureRect) {
4147
4215
  clone.style.width = `${textureRect.width}px`;
4148
4216
  clone.style.height = `${textureRect.height}px`;
@@ -4162,20 +4230,24 @@ function prepareRenderedGeniePreviewCloneForTexture(clone, textureRect) {
4162
4230
  previewElement.style.boxShadow = "none";
4163
4231
  previewElement.style.transform = `translateY(${renderedGeniePreviewHeaderOffsetPx}px)`;
4164
4232
  }
4165
- async function renderPreparedElementTexture({
4166
- clone,
4167
- images,
4168
- rect
4169
- }) {
4233
+ function resolveGenieTextureOutputSize(rect, limits) {
4234
+ const scale = limits ? Math.min(1, limits.maxWidth / rect.width, limits.maxHeight / rect.height) : genieSnapshotScale;
4235
+ return {
4236
+ height: Math.max(1, Math.round(rect.height * scale)),
4237
+ width: Math.max(1, Math.round(rect.width * scale))
4238
+ };
4239
+ }
4240
+ async function renderPreparedElementTexture({ clone, images, rect }, outputLimits) {
4170
4241
  await inlineCloneImageResources({
4171
4242
  cloneRoot: clone,
4172
4243
  images
4173
4244
  });
4174
- const svgTexture = createGenieSvgTexture(clone, rect);
4245
+ const outputSize = resolveGenieTextureOutputSize(rect, outputLimits);
4246
+ const svgTexture = createGenieSvgTexture(clone, rect, outputSize);
4175
4247
  const image = await loadImageFromSvg(svgTexture);
4176
4248
  const canvas = document.createElement("canvas");
4177
- canvas.width = Math.max(1, Math.round(rect.width * genieSnapshotScale));
4178
- canvas.height = Math.max(1, Math.round(rect.height * genieSnapshotScale));
4249
+ canvas.width = outputSize.width;
4250
+ canvas.height = outputSize.height;
4179
4251
  const context = canvas.getContext("2d");
4180
4252
  if (!context) {
4181
4253
  return null;
@@ -4185,9 +4257,9 @@ async function renderPreparedElementTexture({
4185
4257
  context.drawImage(image, 0, 0, canvas.width, canvas.height);
4186
4258
  return { canvas, rect };
4187
4259
  }
4188
- async function captureElementTexture(element) {
4260
+ async function captureElementTexture(element, outputLimits) {
4189
4261
  const preparedCapture = prepareGenieTextureCapture(element);
4190
- return preparedCapture ? renderPreparedElementTexture(preparedCapture) : null;
4262
+ return preparedCapture ? renderPreparedElementTexture(preparedCapture, outputLimits) : null;
4191
4263
  }
4192
4264
  function createDockPreviewDataUrl(canvas) {
4193
4265
  if (canvas.width <= 0 || canvas.height <= 0) {
@@ -4198,6 +4270,9 @@ function createDockPreviewDataUrl(canvas) {
4198
4270
  dockPreviewMaxWidth / canvas.width,
4199
4271
  dockPreviewMaxHeight / canvas.height
4200
4272
  );
4273
+ if (scale === 1) {
4274
+ return canvas.toDataURL("image/png");
4275
+ }
4201
4276
  const output = document.createElement("canvas");
4202
4277
  output.width = Math.max(1, Math.round(canvas.width * scale));
4203
4278
  output.height = Math.max(1, Math.round(canvas.height * scale));
@@ -4282,7 +4357,10 @@ async function captureWorkbenchNodePreviewImage(nodeID, options = {}) {
4282
4357
  if (!captureTarget) {
4283
4358
  return null;
4284
4359
  }
4285
- const texture = await captureElementTexture(captureTarget).catch(() => null);
4360
+ const texture = await captureElementTexture(captureTarget, {
4361
+ maxHeight: dockPreviewMaxHeight,
4362
+ maxWidth: dockPreviewMaxWidth
4363
+ }).catch(() => null);
4286
4364
  const previewImageUrl = texture ? createDockPreviewDataUrl(texture.canvas) : null;
4287
4365
  writeCachedWorkbenchNodePreviewImage(nodeID, previewImageUrl);
4288
4366
  return previewImageUrl;
@@ -4308,7 +4386,10 @@ function persistWorkbenchNodePreviewImage(node, previewImageUrl, input) {
4308
4386
  }
4309
4387
  input.dockPreviewCache.write({ key, previewImageUrl });
4310
4388
  }
4311
- function createGenieSvgTexture(element, rect) {
4389
+ function createGenieSvgTexture(element, rect, outputSize = {
4390
+ height: rect.height,
4391
+ width: rect.width
4392
+ }) {
4312
4393
  const svgNamespace = "http://www.w3.org/2000/svg";
4313
4394
  const svgDocument = document.implementation.createDocument(
4314
4395
  svgNamespace,
@@ -4317,15 +4398,15 @@ function createGenieSvgTexture(element, rect) {
4317
4398
  );
4318
4399
  const svg = svgDocument.documentElement;
4319
4400
  svg.setAttribute("xmlns", svgNamespace);
4320
- svg.setAttribute("width", String(rect.width));
4321
- svg.setAttribute("height", String(rect.height));
4401
+ svg.setAttribute("width", String(outputSize.width));
4402
+ svg.setAttribute("height", String(outputSize.height));
4322
4403
  svg.setAttribute("viewBox", `0 0 ${rect.width} ${rect.height}`);
4323
4404
  const foreignObject = svgDocument.createElementNS(
4324
4405
  svgNamespace,
4325
4406
  "foreignObject"
4326
4407
  );
4327
- foreignObject.setAttribute("width", "100%");
4328
- foreignObject.setAttribute("height", "100%");
4408
+ foreignObject.setAttribute("width", String(rect.width));
4409
+ foreignObject.setAttribute("height", String(rect.height));
4329
4410
  foreignObject.setAttribute("x", "0");
4330
4411
  foreignObject.setAttribute("y", "0");
4331
4412
  foreignObject.appendChild(svgDocument.importNode(element, true));