@kokoa/clotho-editor 0.3.0 → 0.4.0

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.
@@ -1,4 +1,4 @@
1
- import { updateCanvas, getDef, subscribe, updateMeta, mountEditorPlugins, setSelection, getSelection, setDef, undo, redo, downloadAnimationJson, importAnimation, markClean, updateSettings, isDirty, isDraft, canUndo, canRedo, uniqueChapterId, getCurrentTime, addChapter, uniqueElementId, registerExternalAsset, addElement, registerDataUriAsset, listAnimations, setDraft, saveAnimation, deleteAnimation, duplicateAnimation, createAnimation, getSelectedElementIds, deleteElement, deleteChapter, deleteEffect, updateChapter, isGroup, ungroupElement, groupElements, moveElementToEnd, moveElementToFront, reorderElement, loadAnimation, getCurrentSnapshot, setElementValueAtTime, findContainingGroup, isElementSelected, updateElementBase, createLayout, detachFromLayout, addCheckpoint, uniqueCheckpointId, deleteCheckpoint, addCameraFocus, updateCameraFocus, deleteCameraFocus, setCameraKeyframe, removeCameraKeyframe, removeCameraTrack, setCurrentTime, clearCamera, uniqueEffectId, addEffect, removeAppearance, removeTrack, removeTrackKeyframe, setTrackKeyframe, addAppearance, beginTransient, endTransient, getCamera, jumpBack, jumpForward, getHistory, promoteDraftToSaved, toggleSelectionFor, cameraControlAt, moveGroupBy, placeholderImageUrl, groupBbox, hasCamera, layoutIdsFor, findLayoutCollisions, updateLocales, updateData, updateResponsive, updateDuration, updateCheckpoint, setCameraStrokeScaling, updateAppearance, updateEffect, moveCameraKeyframe, childIdsOf } from './chunk-N47HF4VB.js';
1
+ import { updateCanvas, getDef, subscribe, updateMeta, mountEditorPlugins, setSelection, getSelection, setDef, undo, redo, downloadAnimationJson, importAnimation, markClean, updateSettings, isDirty, isDraft, canUndo, canRedo, uniqueChapterId, getCurrentTime, addChapter, uniqueElementId, registerExternalAsset, addElement, registerDataUriAsset, listAnimations, setDraft, saveAnimation, deleteAnimation, duplicateAnimation, createAnimation, getSelectedElementIds, deleteElement, deleteChapter, deleteEffect, updateChapter, isGroup, ungroupElement, groupElements, moveElementToEnd, moveElementToFront, reorderElement, loadAnimation, getCurrentSnapshot, setElementValueAtTime, findContainingGroup, isElementSelected, updateElementBase, createLayout, detachFromLayout, addCheckpoint, uniqueCheckpointId, deleteCheckpoint, addCameraFocus, updateCameraFocus, deleteCameraFocus, setCameraKeyframe, removeCameraKeyframe, removeCameraTrack, setCurrentTime, clearCamera, uniqueEffectId, addEffect, removeAppearance, removeTrack, removeTrackKeyframe, setTrackKeyframe, addAppearance, beginTransient, endTransient, getCamera, jumpBack, jumpForward, getHistory, promoteDraftToSaved, toggleSelectionFor, cameraControlAt, moveGroupBy, placeholderImageUrl, groupBbox, hasCamera, layoutIdsFor, findLayoutCollisions, updateEffect, updateLocales, updateData, updateResponsive, updateDuration, updateCheckpoint, setCameraStrokeScaling, updateAppearance, moveCameraKeyframe, childIdsOf, replaceEffect } from './chunk-WOP52KFW.js';
2
2
  import { activeAppearance, animationDocumentSchema, bindablePropertiesFor, computeCamera, resolveAsset, compileDataBindings } from '@kokoa/clotho';
3
3
 
4
4
  // src/legacy/grid.ts
@@ -2237,6 +2237,8 @@ function render2() {
2237
2237
  }
2238
2238
  const cameraFrame = renderCameraFrame(def);
2239
2239
  if (cameraFrame) canvasEl.appendChild(cameraFrame);
2240
+ const spotlightPreview = renderSpotlightPreview(def, snap2, elementsById);
2241
+ if (spotlightPreview) canvasEl.appendChild(spotlightPreview);
2240
2242
  const selection = getSelection();
2241
2243
  if (selection.kind === "element") {
2242
2244
  const selEl = elementsById.get(selection.elementId);
@@ -2548,6 +2550,64 @@ function moveCameraDrag(e) {
2548
2550
  updateCameraFocus(drag.focusIndex, { padding: next.padding });
2549
2551
  }
2550
2552
  }
2553
+ function renderSpotlightPreview(def, snap2, elementsById) {
2554
+ const selection = getSelection();
2555
+ if (selection.kind !== "effect") return null;
2556
+ const effect = def.effects.find(
2557
+ (candidate) => candidate.id === selection.effectId
2558
+ );
2559
+ if (!effect || effect.type !== "spotlight") return null;
2560
+ const boxes = effect.elementIds.map((id) => {
2561
+ const el = elementsById.get(id);
2562
+ const state = el ? snap2.get(id) : void 0;
2563
+ return el && state ? elementBBox(el, state) : null;
2564
+ }).filter(
2565
+ (box) => box !== null
2566
+ );
2567
+ if (boxes.length === 0) return null;
2568
+ const { width, height } = def.canvas;
2569
+ const pad = effect.padding;
2570
+ const g = document.createElementNS(SVG_NS, "g");
2571
+ g.setAttribute("class", "studio-spotlight-preview");
2572
+ g.setAttribute("pointer-events", "none");
2573
+ const holes = [];
2574
+ if (effect.shape === "elements") {
2575
+ for (const box of boxes) {
2576
+ holes.push(
2577
+ `M${box.x - pad} ${box.y - pad}H${box.x + box.w + pad}V${box.y + box.h + pad}H${box.x - pad}Z`
2578
+ );
2579
+ }
2580
+ } else {
2581
+ const x = Math.min(...boxes.map((box) => box.x)) - pad;
2582
+ const y = Math.min(...boxes.map((box) => box.y)) - pad;
2583
+ const right = Math.max(...boxes.map((box) => box.x + box.w)) + pad;
2584
+ const bottom = Math.max(...boxes.map((box) => box.y + box.h)) + pad;
2585
+ if (effect.shape === "circle") {
2586
+ const cx = (x + right) / 2;
2587
+ const cy = (y + bottom) / 2;
2588
+ const r = Math.hypot(right - x, bottom - y) / 2;
2589
+ holes.push(
2590
+ `M${cx - r} ${cy}a${r} ${r} 0 1 0 ${r * 2} 0a${r} ${r} 0 1 0 ${-r * 2} 0Z`
2591
+ );
2592
+ } else {
2593
+ holes.push(`M${x} ${y}H${right}V${bottom}H${x}Z`);
2594
+ }
2595
+ }
2596
+ const scrim = document.createElementNS(SVG_NS, "path");
2597
+ scrim.setAttribute("d", `M0 0H${width}V${height}H0Z ${holes.join(" ")}`);
2598
+ scrim.setAttribute("fill-rule", "evenodd");
2599
+ scrim.setAttribute("fill", effect.dimColor ?? "#0b1120");
2600
+ scrim.setAttribute("opacity", String(Math.max(effect.dim, 0.15)));
2601
+ g.appendChild(scrim);
2602
+ const label = document.createElementNS(SVG_NS, "text");
2603
+ label.setAttribute("x", "8");
2604
+ label.setAttribute("y", "18");
2605
+ label.setAttribute("class", "studio-spotlight-preview-label");
2606
+ const approximated = effect.shape === "elements" ? " \xB7 \uC2E4\uB8E8\uC5E3\uC740 \uC0C1\uC790\uB85C \uADFC\uC0AC" : "";
2607
+ label.textContent = `\u{1F526} ${effect.id} \xB7 ${effect.time}\u2013${effect.time + effect.duration}ms${approximated}`;
2608
+ g.appendChild(label);
2609
+ return g;
2610
+ }
2551
2611
  function cameraHandles(view, control) {
2552
2612
  const g = document.createElementNS(SVG_NS, "g");
2553
2613
  if (control.kind === "tracks") {
@@ -3192,6 +3252,7 @@ function renderInner() {
3192
3252
  <option value="highlight">highlight</option>
3193
3253
  <option value="pulse">pulse</option>
3194
3254
  <option value="flow">flow</option>
3255
+ <option value="spotlight">spotlight</option>
3195
3256
  </select>
3196
3257
  <button type="button" class="studio-btn" data-add-effect>\uFF0B \uD6A8\uACFC</button>
3197
3258
  </div>
@@ -3283,12 +3344,40 @@ function renderInner() {
3283
3344
  setSelection({ kind: "none" });
3284
3345
  return;
3285
3346
  }
3347
+ const typeOpts = ["highlight", "pulse", "flow", "spotlight"].map(
3348
+ (t) => `<option value="${t}" ${t === eff.type ? "selected" : ""}>${t}</option>`
3349
+ ).join("");
3350
+ if (eff.type === "spotlight") {
3351
+ const shapeOpts = ["bbox", "circle", "elements"].map(
3352
+ (shape) => `<option value="${shape}" ${shape === eff.shape ? "selected" : ""}>${shape}${shape === "bbox" ? " (\uAE30\uBCF8)" : ""}</option>`
3353
+ ).join("");
3354
+ panelEl.innerHTML = `
3355
+ ${timeHint}
3356
+ <div class="studio-props-header"><span class="studio-props-header-title">${escapeHtml2(eff.id)}</span><span class="studio-props-header-type">spotlight</span></div>
3357
+ <label class="studio-field"><span>type</span><select data-prop-key="effect.type">${typeOpts}</select></label>
3358
+ <p class="studio-props-empty" style="margin:0 0 0.4rem">\uB300\uC0C1\uC740 \uADF8\uB300\uB85C \uB450\uACE0 \uB098\uBA38\uC9C0\uB97C \uC5B4\uB461\uAC8C \uD569\uB2C8\uB2E4. \uB300\uC0C1\uC758 \uC0C9\uC774 \uC815\uBCF4\uC778 \uBB38\uC11C\uC5D0\uC11C <code>highlight</code> \uB300\uC2E0 \uC501\uB2C8\uB2E4.</p>
3359
+ ${numberField("time (ms)", "effect.time", eff.time, 50)}
3360
+ ${numberField("duration (ms)", "effect.duration", eff.duration, 50)}
3361
+ ${numberField("fadeIn (ms) \xB7 \uC591 \uB05D\uC5D0 \uC801\uC6A9", "effect.fadeIn", eff.fadeIn, 50)}
3362
+ <div class="studio-camera-field-label">\uB300\uC0C1 \uC694\uC18C (${eff.elementIds.length})</div>
3363
+ ${elementCheckList(def, "data-spotlight-target", eff.id, eff.elementIds)}
3364
+ <label class="studio-field"><span>shape</span><select data-prop-key="effect.shape">${shapeOpts}</select></label>
3365
+ <p class="studio-camera-hint">\uB300\uC0C1\uC774 \uBB34\uB300 \uC5EC\uAE30\uC800\uAE30 \uD769\uC5B4\uC838 \uC788\uC73C\uBA74 <code>elements</code>\uB97C \uC4F0\uC138\uC694. <code>bbox</code>\uB294 \uC14B\uC744 \uB2F4\uB290\uB77C \uADF8 \uC0AC\uC774\uC758 \uAD00\uACC4\uC5C6\uB294 \uC694\uC18C\uAE4C\uC9C0 \uBC1D\uD799\uB2C8\uB2E4.</p>
3366
+ ${rangeField("padding", "effect.padding", eff.padding, 0, 80, 1)}
3367
+ <div class="studio-props-header" style="margin-top:0.6rem"><span class="studio-props-header-title">\uAC00\uB824\uC9C0\uB294 \uCABD</span></div>
3368
+ ${rangeField("dim", "effect.dim", eff.dim, 0, 1, 0.01, "0\uC774\uBA74 \uC5B4\uB461\uAC8C \uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.")}
3369
+ ${colorField("dimColor", "effect.dimColor", eff.dimColor ?? "#0b1120")}
3370
+ <p class="studio-camera-hint">\uBE44\uC6CC \uB450\uBA74 \uD14C\uB9C8 \uD1A0\uD070\uC744 \uC501\uB2C8\uB2E4 \u2014 \uB77C\uC774\uD2B8\xB7\uB2E4\uD06C \uBAA8\uB450 near-black.</p>
3371
+ <div class="studio-props-header" style="margin-top:0.6rem"><span class="studio-props-header-title">\uBE44\uCD94\uB294 \uCABD</span></div>
3372
+ ${rangeField("lit", "effect.lit", eff.lit, 0, 1, 0.01, "0\uC774\uBA74 \uB300\uC0C1\uC740 \uC790\uAE30 \uC0C9 \uADF8\uB300\uB85C\uC785\uB2C8\uB2E4. \uC870\uBA85\uC758 \uC824\uCC98\uB7FC \uC0C9\uC744 \uC5B9\uC73C\uB824\uBA74 \uC62C\uB9AC\uC138\uC694.")}
3373
+ ${colorField("litColor", "effect.litColor", eff.litColor)}
3374
+ <button type="button" class="studio-btn studio-btn-danger" data-delete-effect style="margin-top:0.6rem">\u{1F5D1} \uD6A8\uACFC \uC0AD\uC81C</button>
3375
+ `;
3376
+ return;
3377
+ }
3286
3378
  const elemOpts = def.elements.map(
3287
3379
  (e) => `<option value="${escapeHtml2(e.id)}" ${e.id === eff.elementId ? "selected" : ""}>${escapeHtml2(e.id)}</option>`
3288
3380
  ).join("");
3289
- const typeOpts = ["highlight", "pulse", "flow"].map(
3290
- (t) => `<option value="${t}" ${t === eff.type ? "selected" : ""}>${t}</option>`
3291
- ).join("");
3292
3381
  const specific = eff.type === "highlight" ? colorField("color", "effect.color", eff.color) : eff.type === "pulse" ? numberField("scale", "effect.scale", eff.scale, 0.05) : colorField("color", "effect.color", eff.color) + numberField("particles", "effect.particles", eff.particles, 1) + numberField("radius", "effect.radius", eff.radius, 0.5);
3293
3382
  panelEl.innerHTML = `
3294
3383
  ${timeHint}
@@ -3313,13 +3402,13 @@ function rangeField(label, key, value, min, max, step, hint) {
3313
3402
  ${hint ? `<p class="studio-camera-hint">${escapeHtml2(hint)}</p>` : ""}
3314
3403
  </div>`;
3315
3404
  }
3316
- function focusTargetPicker(def, index, selected) {
3405
+ function elementCheckList(def, attribute, key, selected) {
3317
3406
  const chosen = new Set(selected);
3318
3407
  const rows = def.elements.map((element) => {
3319
3408
  const name = friendlyElementLabel(element);
3320
3409
  const same = name === element.id;
3321
3410
  return `<label class="studio-camera-target ${chosen.has(element.id) ? "is-on" : ""}">
3322
- <input type="checkbox" data-camera-target="${index}" value="${escapeHtml2(element.id)}" ${chosen.has(element.id) ? "checked" : ""} />
3411
+ <input type="checkbox" ${attribute}="${escapeHtml2(key)}" value="${escapeHtml2(element.id)}" ${chosen.has(element.id) ? "checked" : ""} />
3323
3412
  <span class="studio-camera-target-name">${escapeHtml2(name)}</span>
3324
3413
  ${same ? "" : `<span class="studio-camera-target-id">${escapeHtml2(element.id)}</span>`}
3325
3414
  <span class="studio-camera-target-type">${element.type}</span>
@@ -3366,7 +3455,7 @@ function renderCameraForm(def, focusIndex) {
3366
3455
  </div>
3367
3456
  </div>
3368
3457
  <div class="studio-camera-field-label">\uB300\uC0C1 \uC694\uC18C (${entry.elementIds.length})</div>
3369
- ${focusTargetPicker(def, index, entry.elementIds)}
3458
+ ${elementCheckList(def, "data-camera-target", String(index), entry.elementIds)}
3370
3459
  ${rangeField("padding", `camera.focus.${index}.padding`, entry.padding, 0, 200, 2, "\uB300\uC0C1 \uC8FC\uBCC0 \uC5EC\uBC31. \uD074\uC218\uB85D \uB113\uAC8C \uC7A1\uC2B5\uB2C8\uB2E4.")}
3371
3460
  ${rangeField("maxZoom", `camera.focus.${index}.maxZoom`, entry.maxZoom, 1, 8, 0.1, "\uD655\uB300 \uC0C1\uD55C. \uC791\uC740 \uC694\uC18C \uD558\uB098\uAC00 \uD654\uBA74\uC744 \uCC44\uC6B0\uB294 \uAC83\uC744 \uB9C9\uC2B5\uB2C8\uB2E4.")}
3372
3461
  <button type="button" class="studio-btn studio-btn-danger" data-delete-camera-focus="${index}">\u{1F5D1} focus \uC0AD\uC81C</button>
@@ -3697,6 +3786,11 @@ function onInput(e) {
3697
3786
  toggleFocusTarget(Number(focusTarget), target.value, target.checked);
3698
3787
  return;
3699
3788
  }
3789
+ const spotTarget = target.dataset.spotlightTarget;
3790
+ if (spotTarget !== void 0 && target instanceof HTMLInputElement) {
3791
+ toggleSpotlightTarget(spotTarget, target.value, target.checked);
3792
+ return;
3793
+ }
3700
3794
  const key = target.dataset.propKey;
3701
3795
  if (!key) return;
3702
3796
  if (target.type === "text" && e.isComposing) return;
@@ -3946,10 +4040,12 @@ function apply(key, value) {
3946
4040
  if (targets) references[token] = targets;
3947
4041
  else delete references[token];
3948
4042
  updateChapter(sel.chapterId, { references });
4043
+ } else if (key === "effect.type" && sel.kind === "effect") {
4044
+ changeEffectType(def, sel.effectId, String(value));
3949
4045
  } else if (key.startsWith("effect.") && sel.kind === "effect") {
3950
4046
  const prop = key.slice(7);
3951
4047
  const patch = {};
3952
- if (prop === "time" || prop === "duration" || prop === "scale" || prop === "particles" || prop === "radius") {
4048
+ if (prop === "time" || prop === "duration" || prop === "scale" || prop === "particles" || prop === "radius" || prop === "dim" || prop === "lit" || prop === "padding" || prop === "fadeIn") {
3953
4049
  patch[prop] = Number(value);
3954
4050
  } else {
3955
4051
  patch[prop] = value;
@@ -3957,6 +4053,52 @@ function apply(key, value) {
3957
4053
  updateEffect(sel.effectId, patch);
3958
4054
  }
3959
4055
  }
4056
+ function changeEffectType(def, effectId, type) {
4057
+ const current = def.effects.find((effect) => effect.id === effectId);
4058
+ if (!current || current.type === type) return;
4059
+ const targets = current.type === "spotlight" ? current.elementIds : [current.elementId];
4060
+ const base = { id: current.id, time: current.time };
4061
+ let next;
4062
+ if (type === "spotlight") {
4063
+ next = {
4064
+ ...base,
4065
+ type: "spotlight",
4066
+ elementIds: targets,
4067
+ duration: Math.max(current.duration, 800),
4068
+ dim: 0.7,
4069
+ lit: 0,
4070
+ litColor: "#fde68a",
4071
+ shape: "bbox",
4072
+ padding: 12,
4073
+ fadeIn: 200
4074
+ };
4075
+ } else {
4076
+ const single = {
4077
+ ...base,
4078
+ elementId: targets[0] ?? "",
4079
+ duration: current.duration
4080
+ };
4081
+ next = type === "pulse" ? { ...single, type: "pulse", scale: 1.12 } : type === "flow" ? {
4082
+ ...single,
4083
+ type: "flow",
4084
+ color: "#facc15",
4085
+ particles: 3,
4086
+ radius: 4
4087
+ } : { ...single, type: "highlight", color: "#facc15" };
4088
+ }
4089
+ replaceEffect(effectId, next);
4090
+ }
4091
+ function toggleSpotlightTarget(effectId, id, on) {
4092
+ const def = getDef();
4093
+ const effect = def?.effects.find((candidate) => candidate.id === effectId);
4094
+ if (!effect || effect.type !== "spotlight") return;
4095
+ const next = on ? [...effect.elementIds, id] : effect.elementIds.filter((target) => target !== id);
4096
+ if (next.length === 0) {
4097
+ render3();
4098
+ return;
4099
+ }
4100
+ updateEffect(effectId, { elementIds: next });
4101
+ }
3960
4102
  function parseBindingFallback(value) {
3961
4103
  if (value === "") return void 0;
3962
4104
  if (value === "true") return true;
@@ -4147,7 +4289,22 @@ function onClick2(e) {
4147
4289
  duration: 500
4148
4290
  };
4149
4291
  let eff;
4150
- if (type === "highlight")
4292
+ if (type === "spotlight") {
4293
+ const selected = sel.kind === "element" ? [sel.elementId] : sel.kind === "elements" ? [...sel.elementIds] : [firstEl.id];
4294
+ eff = {
4295
+ id,
4296
+ type: "spotlight",
4297
+ elementIds: selected,
4298
+ time: getCurrentTime(),
4299
+ duration: 1200,
4300
+ dim: 0.7,
4301
+ lit: 0,
4302
+ litColor: "#fde68a",
4303
+ shape: "bbox",
4304
+ padding: 12,
4305
+ fadeIn: 200
4306
+ };
4307
+ } else if (type === "highlight")
4151
4308
  eff = { ...base, type: "highlight", color: "#facc15" };
4152
4309
  else if (type === "pulse") eff = { ...base, type: "pulse", scale: 1.12 };
4153
4310
  else
@@ -17939,5 +18096,5 @@ function reflectState(ui) {
17939
18096
  }
17940
18097
 
17941
18098
  export { getVisibleElementIds, initStudio };
17942
- //# sourceMappingURL=main-DC6D6RNQ.js.map
17943
- //# sourceMappingURL=main-DC6D6RNQ.js.map
18099
+ //# sourceMappingURL=main-HDE5SDKM.js.map
18100
+ //# sourceMappingURL=main-HDE5SDKM.js.map