@lingjingai/script-editor 0.1.20 → 0.1.22

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
@@ -1,5 +1,5 @@
1
1
  import { createContext, memo, useState, useMemo, useCallback, Fragment, useRef, useContext, useEffect, useLayoutEffect, useDeferredValue } from 'react';
2
- import { Plus, Trash2, X, Check, Eraser, ArrowUp, ArrowDown, GripVertical, MessageCircle, Film, Brain, BookOpen, Scissors, ArrowUpToLine, BookText, ChevronRight, FileText, Users, MapPin, Package, Undo2, Redo2, MessageSquareQuote, ArrowLeft, ChevronLeft, Search, Loader2, AlertCircle } from 'lucide-react';
2
+ import { Plus, Trash2, X, Check, ArrowUp, ArrowDown, GripVertical, MessageCircle, Film, Brain, BookOpen, Eraser, Scissors, ArrowUpToLine, BookText, ChevronRight, FileText, Users, MapPin, Package, Undo2, Redo2, MessageSquareQuote, ArrowLeft, ChevronLeft, Search, Loader2, AlertCircle } from 'lucide-react';
3
3
  import { jsxs, jsx, Fragment as Fragment$1 } from 'react/jsx-runtime';
4
4
  import { Virtuoso } from 'react-virtuoso';
5
5
  import { createPortal } from 'react-dom';
@@ -1087,33 +1087,108 @@ function updateSceneField(data, episodeIndex, sceneIndex, field, value) {
1087
1087
  });
1088
1088
  });
1089
1089
  }
1090
- function updateSceneAssetRefState(data, episodeIndex, sceneIndex, field, idKey, targetId, stateId) {
1090
+ function updateSceneActorRefState(scene, actorId, stateId) {
1091
+ const context = getSceneContext(scene);
1092
+ let matched = false;
1093
+ let changed = false;
1094
+ const actors = (context.actors ?? []).map((item) => {
1095
+ if ((item.actor_id ?? "").trim() !== actorId) return item;
1096
+ matched = true;
1097
+ if ((item.state_id ?? null) === stateId) return item;
1098
+ changed = true;
1099
+ return { ...item, state_id: stateId };
1100
+ });
1101
+ if (!matched) {
1102
+ changed = true;
1103
+ actors.push({ actor_id: actorId, state_id: stateId });
1104
+ }
1105
+ return changed ? setSceneContext(scene, { actors }) : scene;
1106
+ }
1107
+ function updateSceneLocationRefState(scene, locationId, stateId) {
1108
+ const context = getSceneContext(scene);
1109
+ let matched = false;
1110
+ let changed = false;
1111
+ const locations = (context.locations ?? []).map((item) => {
1112
+ if ((item.location_id ?? "").trim() !== locationId) return item;
1113
+ matched = true;
1114
+ if ((item.state_id ?? null) === stateId) return item;
1115
+ changed = true;
1116
+ return { ...item, state_id: stateId };
1117
+ });
1118
+ if (!matched) {
1119
+ changed = true;
1120
+ locations.push({ location_id: locationId, state_id: stateId });
1121
+ }
1122
+ return changed ? setSceneContext(scene, { locations }) : scene;
1123
+ }
1124
+ function updateScenePropRefState(scene, propId, stateId) {
1125
+ const context = getSceneContext(scene);
1126
+ let matched = false;
1127
+ let changed = false;
1128
+ const props = (context.props ?? []).map((item) => {
1129
+ if ((item.prop_id ?? "").trim() !== propId) return item;
1130
+ matched = true;
1131
+ if ((item.state_id ?? null) === stateId) return item;
1132
+ changed = true;
1133
+ return { ...item, state_id: stateId };
1134
+ });
1135
+ if (!matched) {
1136
+ changed = true;
1137
+ props.push({ prop_id: propId, state_id: stateId });
1138
+ }
1139
+ return changed ? setSceneContext(scene, { props }) : scene;
1140
+ }
1141
+ function updateSceneAssetRefState(data, episodeIndex, sceneIndex, kind, targetId, stateId) {
1091
1142
  const normalizedTarget = targetId.trim();
1092
1143
  const normalizedState = stateId?.trim() || null;
1093
1144
  if (!normalizedTarget) return data;
1094
1145
  return withSceneUpdated(data, episodeIndex, sceneIndex, (scene) => {
1095
- const context = getSceneContext(scene);
1096
- const list = context[field] || [];
1097
- let matched = false;
1098
- const next = list.map((item) => {
1099
- if ((item[idKey] || "").toString().trim() !== normalizedTarget) return item;
1100
- matched = true;
1101
- return { ...item, state_id: normalizedState };
1102
- });
1103
- if (!matched) {
1104
- next.push({ [idKey]: normalizedTarget, state_id: normalizedState });
1105
- }
1106
- return setSceneContext(scene, { [field]: next });
1146
+ if (kind === "actor") return updateSceneActorRefState(scene, normalizedTarget, normalizedState);
1147
+ if (kind === "location") return updateSceneLocationRefState(scene, normalizedTarget, normalizedState);
1148
+ return updateScenePropRefState(scene, normalizedTarget, normalizedState);
1107
1149
  });
1108
1150
  }
1109
1151
  function updateSceneActorState(data, episodeIndex, sceneIndex, actorId, stateId) {
1110
- return updateSceneAssetRefState(data, episodeIndex, sceneIndex, "actors", "actor_id", actorId, stateId);
1152
+ return updateSceneAssetRefState(data, episodeIndex, sceneIndex, "actor", actorId, stateId);
1111
1153
  }
1112
1154
  function updateSceneLocationState(data, episodeIndex, sceneIndex, locationId, stateId) {
1113
- return updateSceneAssetRefState(data, episodeIndex, sceneIndex, "locations", "location_id", locationId, stateId);
1155
+ return updateSceneAssetRefState(data, episodeIndex, sceneIndex, "location", locationId, stateId);
1114
1156
  }
1115
1157
  function updateScenePropState(data, episodeIndex, sceneIndex, propId, stateId) {
1116
- return updateSceneAssetRefState(data, episodeIndex, sceneIndex, "props", "prop_id", propId, stateId);
1158
+ return updateSceneAssetRefState(data, episodeIndex, sceneIndex, "prop", propId, stateId);
1159
+ }
1160
+ function updateSceneAssetStateBindings(data, kind, assetId, targets, stateId) {
1161
+ const normalizedAssetId = assetId.trim();
1162
+ if (!normalizedAssetId || targets.length === 0) return data;
1163
+ const normalizedState = stateId?.trim() || null;
1164
+ const byEpisode = /* @__PURE__ */ new Map();
1165
+ for (const target of targets) {
1166
+ if (target.episodeIndex < 0 || target.sceneIndex < 0) continue;
1167
+ let scenes = byEpisode.get(target.episodeIndex);
1168
+ if (!scenes) {
1169
+ scenes = /* @__PURE__ */ new Set();
1170
+ byEpisode.set(target.episodeIndex, scenes);
1171
+ }
1172
+ scenes.add(target.sceneIndex);
1173
+ }
1174
+ if (byEpisode.size === 0) return data;
1175
+ let changed = false;
1176
+ const episodes = (data.episodes ?? []).map((episode, episodeIndex) => {
1177
+ const sceneIndexes = byEpisode.get(episodeIndex);
1178
+ if (!sceneIndexes) return episode;
1179
+ let episodeChanged = false;
1180
+ const scenes = episode.scenes?.map((scene, sceneIndex) => {
1181
+ if (!sceneIndexes.has(sceneIndex)) return scene;
1182
+ const nextScene = kind === "actor" ? updateSceneActorRefState(scene, normalizedAssetId, normalizedState) : kind === "location" ? updateSceneLocationRefState(scene, normalizedAssetId, normalizedState) : updateScenePropRefState(scene, normalizedAssetId, normalizedState);
1183
+ if (nextScene !== scene) {
1184
+ episodeChanged = true;
1185
+ changed = true;
1186
+ }
1187
+ return nextScene;
1188
+ });
1189
+ return scenes && episodeChanged ? { ...episode, scenes } : episode;
1190
+ });
1191
+ return changed ? { ...data, episodes } : data;
1117
1192
  }
1118
1193
  function updateSceneItemContent(data, episodeIndex, sceneIndex, itemIndex, content) {
1119
1194
  return withSceneUpdated(data, episodeIndex, sceneIndex, (scene) => {
@@ -2782,56 +2857,6 @@ function removeEntityState(data, kind, id, stateId) {
2782
2857
  states: (e.states ?? []).filter((s) => (s.state_id ?? "").trim() !== stateId)
2783
2858
  }));
2784
2859
  }
2785
- var CONFIRM_TTL_MS = 2500;
2786
- function DeleteConfirmButton({
2787
- onConfirm,
2788
- disabled,
2789
- targetScope,
2790
- title = "\u5220\u9664\uFF1F"
2791
- }) {
2792
- const [confirming, setConfirming] = useState(false);
2793
- const timerRef = useRef(null);
2794
- useEffect(
2795
- () => () => {
2796
- if (timerRef.current) clearTimeout(timerRef.current);
2797
- },
2798
- []
2799
- );
2800
- const cancel = () => {
2801
- if (timerRef.current) {
2802
- clearTimeout(timerRef.current);
2803
- timerRef.current = null;
2804
- }
2805
- setConfirming(false);
2806
- };
2807
- const handleClick = () => {
2808
- if (confirming) {
2809
- cancel();
2810
- void onConfirm();
2811
- return;
2812
- }
2813
- setConfirming(true);
2814
- if (timerRef.current) clearTimeout(timerRef.current);
2815
- timerRef.current = setTimeout(() => {
2816
- setConfirming(false);
2817
- timerRef.current = null;
2818
- }, CONFIRM_TTL_MS);
2819
- };
2820
- return /* @__PURE__ */ jsx(
2821
- "button",
2822
- {
2823
- type: "button",
2824
- disabled,
2825
- "data-delete-target": targetScope,
2826
- "data-confirming": confirming || void 0,
2827
- onClick: handleClick,
2828
- "aria-label": confirming ? `\u518D\u70B9\u4E00\u6B21\u786E\u8BA4\uFF1A${title}` : title,
2829
- title: confirming ? "\u518D\u70B9\u4E00\u6B21\u786E\u8BA4\uFF082.5 \u79D2\u540E\u81EA\u52A8\u53D6\u6D88\uFF09" : title,
2830
- className: cn("lj-se-del", confirming && "is-confirming"),
2831
- children: confirming ? /* @__PURE__ */ jsx(Check, { className: "lj-se-icon", strokeWidth: 2.2 }) : /* @__PURE__ */ jsx(Eraser, { className: "lj-se-icon", strokeWidth: 1.5 })
2832
- }
2833
- );
2834
- }
2835
2860
  var SPACE_ZH = { interior: "\u5185", exterior: "\u5916" };
2836
2861
  var TIME_ZH = { day: "\u65E5", night: "\u591C", dawn: "\u6668", dusk: "\u66AE" };
2837
2862
  function resolveCueId(item, actorMap, speakerMap) {
@@ -2943,6 +2968,7 @@ function PreviewLine({
2943
2968
  ] });
2944
2969
  }
2945
2970
  var UNASSIGNED = "__unassigned__";
2971
+ var DRAG_THRESHOLD = 4;
2946
2972
  var CUE_COUNT = 8;
2947
2973
  var COL = 32;
2948
2974
  var MIN_EP_WIDTH = 48;
@@ -2952,9 +2978,20 @@ function AssetStateTimeline({
2952
2978
  assetKind,
2953
2979
  refs,
2954
2980
  states: states2,
2981
+ disabled = false,
2982
+ onBind,
2955
2983
  onJump
2956
2984
  }) {
2957
2985
  const [hover, setHover] = useState(null);
2986
+ const [selectedKeys, setSelectedKeys] = useState(() => /* @__PURE__ */ new Set());
2987
+ const [draggingKeys, setDraggingKeys] = useState(() => /* @__PURE__ */ new Set());
2988
+ const [dropLaneId, setDropLaneId] = useState(null);
2989
+ const [box, setBox] = useState(null);
2990
+ const [dragGhost, setDragGhost] = useState(null);
2991
+ const rootRef = useRef(null);
2992
+ const dragRef = useRef(null);
2993
+ const boxRef = useRef(null);
2994
+ const suppressClickRef = useRef(false);
2958
2995
  const closeTimer = useRef(null);
2959
2996
  const cancelClose = () => {
2960
2997
  if (closeTimer.current) {
@@ -2966,6 +3003,7 @@ function AssetStateTimeline({
2966
3003
  cancelClose();
2967
3004
  closeTimer.current = setTimeout(() => setHover(null), 140);
2968
3005
  };
3006
+ useEffect(() => () => cancelClose(), []);
2969
3007
  const cols = [];
2970
3008
  for (const group of refs) for (const item of group.items) cols.push(item);
2971
3009
  const n = cols.length;
@@ -2976,10 +3014,11 @@ function AssetStateTimeline({
2976
3014
  const sid = (c.stateId ?? "").trim();
2977
3015
  return !sid || !definedIds.includes(sid);
2978
3016
  };
2979
- if (cols.some(isUnassigned)) {
3017
+ if (cols.some(isUnassigned) || !disabled && lanes.length > 0) {
2980
3018
  lanes.push({ id: UNASSIGNED, name: lanes.length > 0 ? "\u672A\u6807\u6CE8" : "\u51FA\u73B0", cueIndex: 0 });
2981
3019
  }
2982
3020
  const colMetas = [];
3021
+ const itemByKey = /* @__PURE__ */ new Map();
2983
3022
  const epHeads = [];
2984
3023
  let x = 0;
2985
3024
  for (const group of refs) {
@@ -2988,82 +3027,251 @@ function AssetStateTimeline({
2988
3027
  epHeads.push({ episodeIndex: group.episodeIndex, episodeNumber: group.episodeNumber, width: count * w });
2989
3028
  for (const item of group.items) {
2990
3029
  colMetas.push({ item, left: x, width: w });
3030
+ itemByKey.set(item.key, item);
2991
3031
  x += w;
2992
3032
  }
2993
3033
  }
2994
3034
  const trackWidth = x;
3035
+ useEffect(() => {
3036
+ setSelectedKeys((prev) => {
3037
+ const next = new Set(Array.from(prev).filter((key) => itemByKey.has(key)));
3038
+ return next.size === prev.size ? prev : next;
3039
+ });
3040
+ }, [refs]);
2995
3041
  const center = (m) => m.left + m.width / 2;
2996
3042
  const cueVar = (cueIndex) => cueIndex === 0 ? "var(--lj-se-muted-fg)" : `var(--lj-se-actor-cue-${cueIndex})`;
2997
3043
  const laneMetas = (laneId) => colMetas.filter((m) => laneId === UNASSIGNED ? isUnassigned(m.item) : (m.item.stateId ?? "").trim() === laneId);
2998
- return /* @__PURE__ */ jsxs("div", { className: "lj-se-sttl", children: [
2999
- /* @__PURE__ */ jsxs("div", { className: "lj-se-sttl-axis", children: [
3000
- /* @__PURE__ */ jsx("span", { className: "lj-se-sttl-axis-lab", children: "\u72B6\u6001 \uFF3C \u573A" }),
3001
- /* @__PURE__ */ jsx("span", { className: "lj-se-sttl-axis-eps", style: { width: trackWidth }, children: epHeads.map((g) => /* @__PURE__ */ jsxs("span", { className: "lj-se-sttl-ep", style: { width: g.width }, title: `\u7B2C ${g.episodeNumber} \u96C6`, children: [
3002
- "\u7B2C",
3003
- String(g.episodeNumber).padStart(2, "0"),
3004
- "\u96C6"
3005
- ] }, g.episodeIndex)) })
3006
- ] }),
3007
- lanes.map((lane) => {
3008
- const mine = laneMetas(lane.id);
3009
- return /* @__PURE__ */ jsxs("div", { className: "lj-se-sttl-lane", children: [
3010
- /* @__PURE__ */ jsxs("span", { className: "lj-se-sttl-lane-head", title: lane.name, children: [
3011
- /* @__PURE__ */ jsx("span", { className: "lj-se-sttl-dot", style: { background: cueVar(lane.cueIndex) } }),
3012
- /* @__PURE__ */ jsx("span", { className: "lj-se-sttl-lane-name", children: lane.name }),
3013
- /* @__PURE__ */ jsx("span", { className: "lj-se-sttl-lane-count", children: mine.length })
3044
+ const laneIds = new Set(lanes.map((lane) => lane.id));
3045
+ const stateIdForLane = (laneId) => laneId === UNASSIGNED ? null : laneId;
3046
+ const targetsForKeys = (keys) => Array.from(keys).map((key) => itemByKey.get(key)).filter((item) => !!item).map((item) => ({ episodeIndex: item.episodeIndex, sceneIndex: item.sceneIndex }));
3047
+ const findLaneIdAt = (clientX, clientY) => {
3048
+ const lane = document.elementFromPoint(clientX, clientY)?.closest(".lj-se-sttl-lane");
3049
+ const laneId = lane?.dataset.sttlLaneId;
3050
+ return laneId && laneIds.has(laneId) ? laneId : null;
3051
+ };
3052
+ const selectKeysInBox = (nextBox) => {
3053
+ const left = Math.min(nextBox.startX, nextBox.currentX);
3054
+ const right = Math.max(nextBox.startX, nextBox.currentX);
3055
+ const top = Math.min(nextBox.startY, nextBox.currentY);
3056
+ const bottom = Math.max(nextBox.startY, nextBox.currentY);
3057
+ const next = /* @__PURE__ */ new Set();
3058
+ rootRef.current?.querySelectorAll("[data-sttl-node-key]").forEach((node) => {
3059
+ const r = node.getBoundingClientRect();
3060
+ const x2 = r.left + r.width / 2;
3061
+ const y = r.top + r.height / 2;
3062
+ if (x2 >= left && x2 <= right && y >= top && y <= bottom) {
3063
+ const key = node.dataset.sttlNodeKey;
3064
+ if (key) next.add(key);
3065
+ }
3066
+ });
3067
+ setSelectedKeys(next);
3068
+ };
3069
+ const boxStyle = () => {
3070
+ if (!box || !rootRef.current) return void 0;
3071
+ const r = rootRef.current.getBoundingClientRect();
3072
+ return {
3073
+ left: Math.min(box.startX, box.currentX) - r.left + rootRef.current.scrollLeft,
3074
+ top: Math.min(box.startY, box.currentY) - r.top + rootRef.current.scrollTop,
3075
+ width: Math.abs(box.currentX - box.startX),
3076
+ height: Math.abs(box.currentY - box.startY)
3077
+ };
3078
+ };
3079
+ const clearPointerState = (pointerId) => {
3080
+ if (rootRef.current?.hasPointerCapture(pointerId)) rootRef.current.releasePointerCapture(pointerId);
3081
+ dragRef.current = null;
3082
+ boxRef.current = null;
3083
+ setDraggingKeys(/* @__PURE__ */ new Set());
3084
+ setDropLaneId(null);
3085
+ setBox(null);
3086
+ setDragGhost(null);
3087
+ };
3088
+ const handleNodePointerDown = (e, key) => {
3089
+ if (disabled || e.button !== 0) return;
3090
+ e.stopPropagation();
3091
+ cancelClose();
3092
+ setHover(null);
3093
+ const keys = selectedKeys.has(key) ? new Set(selectedKeys) : /* @__PURE__ */ new Set([key]);
3094
+ setSelectedKeys(keys);
3095
+ dragRef.current = {
3096
+ pointerId: e.pointerId,
3097
+ startX: e.clientX,
3098
+ startY: e.clientY,
3099
+ keys,
3100
+ dragging: false
3101
+ };
3102
+ rootRef.current?.setPointerCapture(e.pointerId);
3103
+ };
3104
+ const dragLabel = (keys) => {
3105
+ if (keys.size !== 1) return `${keys.size} \u573A`;
3106
+ const item = itemByKey.get(Array.from(keys)[0]);
3107
+ return item ? String(item.sceneNumber) : "1 \u573A";
3108
+ };
3109
+ const handleTrackPointerDown = (e) => {
3110
+ if (disabled || e.button !== 0 || e.target instanceof HTMLElement && e.target.closest("[data-sttl-node-key]")) return;
3111
+ const next = {
3112
+ pointerId: e.pointerId,
3113
+ startX: e.clientX,
3114
+ startY: e.clientY,
3115
+ currentX: e.clientX,
3116
+ currentY: e.clientY,
3117
+ moved: false
3118
+ };
3119
+ setSelectedKeys(/* @__PURE__ */ new Set());
3120
+ boxRef.current = next;
3121
+ setBox(next);
3122
+ rootRef.current?.setPointerCapture(e.pointerId);
3123
+ };
3124
+ const handlePointerMove = (e) => {
3125
+ const drag = dragRef.current;
3126
+ if (drag?.pointerId === e.pointerId) {
3127
+ const moved2 = Math.hypot(e.clientX - drag.startX, e.clientY - drag.startY) >= DRAG_THRESHOLD;
3128
+ if (!drag.dragging && moved2) {
3129
+ drag.dragging = true;
3130
+ setDraggingKeys(new Set(drag.keys));
3131
+ }
3132
+ if (drag.dragging) {
3133
+ e.preventDefault();
3134
+ setDragGhost({ x: e.clientX, y: e.clientY, label: dragLabel(drag.keys) });
3135
+ setDropLaneId(findLaneIdAt(e.clientX, e.clientY));
3136
+ }
3137
+ return;
3138
+ }
3139
+ const nextBox = boxRef.current;
3140
+ if (nextBox?.pointerId !== e.pointerId) return;
3141
+ const moved = nextBox.moved || Math.hypot(e.clientX - nextBox.startX, e.clientY - nextBox.startY) >= DRAG_THRESHOLD;
3142
+ const updated = { ...nextBox, currentX: e.clientX, currentY: e.clientY, moved };
3143
+ boxRef.current = updated;
3144
+ setBox(updated);
3145
+ if (updated.moved) selectKeysInBox(updated);
3146
+ };
3147
+ const handlePointerUp = (e) => {
3148
+ const drag = dragRef.current;
3149
+ if (drag?.pointerId === e.pointerId) {
3150
+ if (drag.dragging) {
3151
+ const laneId = dropLaneId ?? findLaneIdAt(e.clientX, e.clientY);
3152
+ if (laneId) {
3153
+ const targets = targetsForKeys(drag.keys);
3154
+ if (targets.length > 0) onBind(targets, stateIdForLane(laneId));
3155
+ }
3156
+ suppressClickRef.current = true;
3157
+ window.setTimeout(() => {
3158
+ suppressClickRef.current = false;
3159
+ }, 0);
3160
+ e.preventDefault();
3161
+ }
3162
+ clearPointerState(e.pointerId);
3163
+ return;
3164
+ }
3165
+ const nextBox = boxRef.current;
3166
+ if (nextBox?.pointerId !== e.pointerId) return;
3167
+ if (nextBox.moved) selectKeysInBox(nextBox);
3168
+ else setSelectedKeys(/* @__PURE__ */ new Set());
3169
+ clearPointerState(e.pointerId);
3170
+ };
3171
+ return /* @__PURE__ */ jsxs(
3172
+ "div",
3173
+ {
3174
+ ref: rootRef,
3175
+ className: cn("lj-se-sttl", !disabled && "is-editable", draggingKeys.size > 0 && "is-dragging"),
3176
+ onPointerMove: handlePointerMove,
3177
+ onPointerUp: handlePointerUp,
3178
+ onPointerCancel: (e) => clearPointerState(e.pointerId),
3179
+ children: [
3180
+ /* @__PURE__ */ jsxs("div", { className: "lj-se-sttl-axis", children: [
3181
+ /* @__PURE__ */ jsx("span", { className: "lj-se-sttl-axis-lab", children: "\u72B6\u6001 \uFF3C \u573A" }),
3182
+ /* @__PURE__ */ jsx("span", { className: "lj-se-sttl-axis-eps", style: { width: trackWidth }, children: epHeads.map((g) => /* @__PURE__ */ jsxs("span", { className: "lj-se-sttl-ep", style: { width: g.width }, title: `\u7B2C ${g.episodeNumber} \u96C6`, children: [
3183
+ "\u7B2C",
3184
+ String(g.episodeNumber).padStart(2, "0"),
3185
+ "\u96C6"
3186
+ ] }, g.episodeIndex)) })
3014
3187
  ] }),
3015
- /* @__PURE__ */ jsxs("span", { className: "lj-se-sttl-track", style: { width: trackWidth, "--cue": cueVar(lane.cueIndex) }, children: [
3016
- colMetas.map((m, i) => /* @__PURE__ */ jsx("span", { className: "lj-se-sttl-col", style: { left: m.left } }, `col-${i}`)),
3017
- mine.map((m, k) => {
3018
- const next = mine[k + 1];
3019
- if (!next) return null;
3020
- return /* @__PURE__ */ jsx(
3188
+ lanes.map((lane) => {
3189
+ const mine = laneMetas(lane.id);
3190
+ return /* @__PURE__ */ jsxs("div", { className: cn("lj-se-sttl-lane", dropLaneId === lane.id && "is-drop"), "data-sttl-lane-id": lane.id, children: [
3191
+ /* @__PURE__ */ jsxs("span", { className: "lj-se-sttl-lane-head", title: lane.name, children: [
3192
+ /* @__PURE__ */ jsx("span", { className: "lj-se-sttl-dot", style: { background: cueVar(lane.cueIndex) } }),
3193
+ /* @__PURE__ */ jsx("span", { className: "lj-se-sttl-lane-name", children: lane.name }),
3194
+ /* @__PURE__ */ jsx("span", { className: "lj-se-sttl-lane-count", children: mine.length })
3195
+ ] }),
3196
+ /* @__PURE__ */ jsxs(
3021
3197
  "span",
3022
3198
  {
3023
- className: "lj-se-sttl-flow",
3024
- style: { left: center(m), width: center(next) - center(m) }
3025
- },
3026
- `flow-${m.item.key}`
3027
- );
3028
- }),
3029
- mine.map((m) => /* @__PURE__ */ jsx(
3030
- "button",
3031
- {
3032
- type: "button",
3033
- className: "lj-se-sttl-node",
3034
- style: { left: center(m) },
3035
- title: m.item.label,
3036
- onClick: () => onJump(m.item.episodeIndex, m.item.sceneIndex),
3037
- onMouseEnter: (e) => {
3038
- cancelClose();
3039
- const r = e.currentTarget.getBoundingClientRect();
3040
- setHover({
3041
- episodeIndex: m.item.episodeIndex,
3042
- sceneIndex: m.item.sceneIndex,
3043
- rect: { top: r.top, bottom: r.bottom, left: r.left, width: r.width },
3044
- host: e.currentTarget.closest(".lj-se-root")
3045
- });
3046
- },
3047
- onMouseLeave: scheduleClose,
3048
- children: m.item.sceneNumber
3049
- },
3050
- m.item.key
3051
- ))
3052
- ] })
3053
- ] }, lane.id);
3054
- }),
3055
- hover ? /* @__PURE__ */ jsx(
3056
- ScenePreviewCard,
3057
- {
3058
- data,
3059
- anchor: hover,
3060
- highlightId: assetId,
3061
- highlightKind: assetKind,
3062
- onEnter: cancelClose,
3063
- onLeave: scheduleClose
3064
- }
3065
- ) : null
3066
- ] });
3199
+ className: "lj-se-sttl-track",
3200
+ style: { width: trackWidth, "--cue": cueVar(lane.cueIndex) },
3201
+ onPointerDown: handleTrackPointerDown,
3202
+ children: [
3203
+ colMetas.map((m, i) => /* @__PURE__ */ jsx("span", { className: "lj-se-sttl-col", style: { left: m.left } }, `col-${i}`)),
3204
+ mine.map((m, k) => {
3205
+ const next = mine[k + 1];
3206
+ if (!next) return null;
3207
+ return /* @__PURE__ */ jsx(
3208
+ "span",
3209
+ {
3210
+ className: "lj-se-sttl-flow",
3211
+ style: { left: center(m), width: center(next) - center(m) }
3212
+ },
3213
+ `flow-${m.item.key}`
3214
+ );
3215
+ }),
3216
+ mine.map((m) => /* @__PURE__ */ jsx(
3217
+ "button",
3218
+ {
3219
+ type: "button",
3220
+ className: cn(
3221
+ "lj-se-sttl-node",
3222
+ selectedKeys.has(m.item.key) && "is-selected",
3223
+ draggingKeys.has(m.item.key) && "is-dragging"
3224
+ ),
3225
+ "data-sttl-node-key": m.item.key,
3226
+ "aria-pressed": selectedKeys.has(m.item.key),
3227
+ style: { left: center(m) },
3228
+ title: m.item.label,
3229
+ onPointerDown: (e) => handleNodePointerDown(e, m.item.key),
3230
+ onClick: (e) => {
3231
+ if (suppressClickRef.current) {
3232
+ suppressClickRef.current = false;
3233
+ e.preventDefault();
3234
+ return;
3235
+ }
3236
+ onJump(m.item.episodeIndex, m.item.sceneIndex);
3237
+ },
3238
+ onMouseEnter: (e) => {
3239
+ if (dragRef.current?.dragging) return;
3240
+ cancelClose();
3241
+ const r = e.currentTarget.getBoundingClientRect();
3242
+ setHover({
3243
+ episodeIndex: m.item.episodeIndex,
3244
+ sceneIndex: m.item.sceneIndex,
3245
+ rect: { top: r.top, bottom: r.bottom, left: r.left, width: r.width },
3246
+ host: e.currentTarget.closest(".lj-se-root")
3247
+ });
3248
+ },
3249
+ onMouseLeave: scheduleClose,
3250
+ children: m.item.sceneNumber
3251
+ },
3252
+ m.item.key
3253
+ ))
3254
+ ]
3255
+ }
3256
+ )
3257
+ ] }, lane.id);
3258
+ }),
3259
+ box ? /* @__PURE__ */ jsx("span", { className: "lj-se-sttl-select-box", style: boxStyle() }) : null,
3260
+ dragGhost ? /* @__PURE__ */ jsx("span", { className: "lj-se-sttl-drag-ghost", style: { left: dragGhost.x, top: dragGhost.y }, children: dragGhost.label }) : null,
3261
+ hover ? /* @__PURE__ */ jsx(
3262
+ ScenePreviewCard,
3263
+ {
3264
+ data,
3265
+ anchor: hover,
3266
+ highlightId: assetId,
3267
+ highlightKind: assetKind,
3268
+ onEnter: cancelClose,
3269
+ onLeave: scheduleClose
3270
+ }
3271
+ ) : null
3272
+ ]
3273
+ }
3274
+ );
3067
3275
  }
3068
3276
  var META = {
3069
3277
  actors: { kind: "actor" },
@@ -3073,6 +3281,38 @@ var META = {
3073
3281
  function newStateId(id) {
3074
3282
  return `st_${id}_${Date.now().toString(36)}`;
3075
3283
  }
3284
+ function StateDeleteButton({ stateName, onConfirm }) {
3285
+ const [open, setOpen] = useState(false);
3286
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
3287
+ /* @__PURE__ */ jsx("button", { type: "button", className: "lj-se-assetcard-state-del", title: "\u5220\u9664\u8BE5\u72B6\u6001", "aria-label": "\u5220\u9664\u8BE5\u72B6\u6001", onClick: () => setOpen(true), children: /* @__PURE__ */ jsx(X, { className: "lj-se-ic-3", strokeWidth: 1.8 }) }),
3288
+ open ? /* @__PURE__ */ jsx("div", { className: "lj-se-modal-backdrop", role: "presentation", onMouseDown: () => setOpen(false), children: /* @__PURE__ */ jsxs("div", { className: "lj-se-modal", role: "dialog", "aria-modal": "true", "aria-labelledby": "lj-se-delete-state-title", onMouseDown: (e) => e.stopPropagation(), children: [
3289
+ /* @__PURE__ */ jsxs("div", { className: "lj-se-modal-head", children: [
3290
+ /* @__PURE__ */ jsx("h2", { id: "lj-se-delete-state-title", className: "lj-se-modal-title", children: "\u5220\u9664\u72B6\u6001" }),
3291
+ /* @__PURE__ */ jsx("button", { type: "button", className: "lj-se-modal-x", "aria-label": "\u5173\u95ED", onClick: () => setOpen(false), children: /* @__PURE__ */ jsx(X, { className: "lj-se-ic-3", strokeWidth: 1.8 }) })
3292
+ ] }),
3293
+ /* @__PURE__ */ jsxs("p", { className: "lj-se-modal-body", children: [
3294
+ "\u786E\u8BA4\u5220\u9664\u300C",
3295
+ stateName || "\u672A\u547D\u540D\u72B6\u6001",
3296
+ "\u300D\uFF1F"
3297
+ ] }),
3298
+ /* @__PURE__ */ jsxs("div", { className: "lj-se-modal-actions", children: [
3299
+ /* @__PURE__ */ jsx("button", { type: "button", className: "lj-se-modal-btn", onClick: () => setOpen(false), children: "\u53D6\u6D88" }),
3300
+ /* @__PURE__ */ jsx(
3301
+ "button",
3302
+ {
3303
+ type: "button",
3304
+ className: "lj-se-modal-btn lj-se-modal-btn-danger",
3305
+ onClick: () => {
3306
+ setOpen(false);
3307
+ onConfirm();
3308
+ },
3309
+ children: "\u5220\u9664"
3310
+ }
3311
+ )
3312
+ ] })
3313
+ ] }) }) : null
3314
+ ] });
3315
+ }
3076
3316
  function AssetCardImpl({
3077
3317
  data,
3078
3318
  group,
@@ -3179,7 +3419,13 @@ function AssetCardImpl({
3179
3419
  onSave: (v) => onEdit((p) => updateEntityStateField(p, kind, assetId, sid, "state_name", v))
3180
3420
  }
3181
3421
  ),
3182
- !disabled && (state.state_id ?? "").trim() ? /* @__PURE__ */ jsx(DeleteConfirmButton, { targetScope: "row", title: "\u5220\u9664\u8BE5\u72B6\u6001\uFF1F", onConfirm: () => onEdit((p) => removeEntityState(p, kind, assetId, sid)) }) : null
3422
+ !disabled && (state.state_id ?? "").trim() ? /* @__PURE__ */ jsx(
3423
+ StateDeleteButton,
3424
+ {
3425
+ stateName: state.state_name ?? state.name ?? "",
3426
+ onConfirm: () => onEdit((p) => removeEntityState(p, kind, assetId, sid))
3427
+ }
3428
+ ) : null
3183
3429
  ] }),
3184
3430
  /* @__PURE__ */ jsx(
3185
3431
  EditableText,
@@ -3206,6 +3452,8 @@ function AssetCardImpl({
3206
3452
  assetKind: kind,
3207
3453
  refs,
3208
3454
  states: states2,
3455
+ disabled,
3456
+ onBind: (targets, stateId) => onEdit((p) => updateSceneAssetStateBindings(p, kind, assetId, targets, stateId)),
3209
3457
  onJump: (episodeIdx, sceneIdx) => onSelect({ kind: "scene", episodeIdx, sceneIdx, from: fromRef, origin: "sidebar" })
3210
3458
  }
3211
3459
  )
@@ -4534,6 +4782,7 @@ __export(mutations_exports, {
4534
4782
  updateEntityName: () => updateEntityName,
4535
4783
  updateEntityStateField: () => updateEntityStateField,
4536
4784
  updateSceneActorState: () => updateSceneActorState,
4785
+ updateSceneAssetStateBindings: () => updateSceneAssetStateBindings,
4537
4786
  updateSceneField: () => updateSceneField,
4538
4787
  updateSceneItemActor: () => updateSceneItemActor,
4539
4788
  updateSceneItemContent: () => updateSceneItemContent,
@@ -4542,6 +4791,56 @@ __export(mutations_exports, {
4542
4791
  updateSceneLocationState: () => updateSceneLocationState,
4543
4792
  updateScenePropState: () => updateScenePropState
4544
4793
  });
4794
+ var CONFIRM_TTL_MS = 2500;
4795
+ function DeleteConfirmButton({
4796
+ onConfirm,
4797
+ disabled,
4798
+ targetScope,
4799
+ title = "\u5220\u9664\uFF1F"
4800
+ }) {
4801
+ const [confirming, setConfirming] = useState(false);
4802
+ const timerRef = useRef(null);
4803
+ useEffect(
4804
+ () => () => {
4805
+ if (timerRef.current) clearTimeout(timerRef.current);
4806
+ },
4807
+ []
4808
+ );
4809
+ const cancel = () => {
4810
+ if (timerRef.current) {
4811
+ clearTimeout(timerRef.current);
4812
+ timerRef.current = null;
4813
+ }
4814
+ setConfirming(false);
4815
+ };
4816
+ const handleClick = () => {
4817
+ if (confirming) {
4818
+ cancel();
4819
+ void onConfirm();
4820
+ return;
4821
+ }
4822
+ setConfirming(true);
4823
+ if (timerRef.current) clearTimeout(timerRef.current);
4824
+ timerRef.current = setTimeout(() => {
4825
+ setConfirming(false);
4826
+ timerRef.current = null;
4827
+ }, CONFIRM_TTL_MS);
4828
+ };
4829
+ return /* @__PURE__ */ jsx(
4830
+ "button",
4831
+ {
4832
+ type: "button",
4833
+ disabled,
4834
+ "data-delete-target": targetScope,
4835
+ "data-confirming": confirming || void 0,
4836
+ onClick: handleClick,
4837
+ "aria-label": confirming ? `\u518D\u70B9\u4E00\u6B21\u786E\u8BA4\uFF1A${title}` : title,
4838
+ title: confirming ? "\u518D\u70B9\u4E00\u6B21\u786E\u8BA4\uFF082.5 \u79D2\u540E\u81EA\u52A8\u53D6\u6D88\uFF09" : title,
4839
+ className: cn("lj-se-del", confirming && "is-confirming"),
4840
+ children: confirming ? /* @__PURE__ */ jsx(Check, { className: "lj-se-icon", strokeWidth: 2.2 }) : /* @__PURE__ */ jsx(Eraser, { className: "lj-se-icon", strokeWidth: 1.5 })
4841
+ }
4842
+ );
4843
+ }
4545
4844
 
4546
4845
  export { DeleteConfirmButton, EMPTY_SELECTION, EditableText, Popover, ScriptContentType, ScriptEditor, SelectField, acceptActionIntoBaseline, acceptSceneIntoBaseline, applyActionAccept, applyActionReject, buildActionDiff, buildDiffFromBaseline, buildScriptNav, buildSelectionFrom, changeSetToDiffScenes, computeChangeSet, computeSceneActionDiff, findBaselineScene, fromToSelection, getActorCueVar, getActorMap, getEpisodeTitle, getLocationMap, getPropMap, getSceneContext, getSelectionEpisodeIdx, getSpeakerMap, isClipSelected, isEpisodeOpen, isSameSelectionTarget, isSceneSelected, mutations_exports as mutations, rejectActionToBaseline, rejectSceneToBaseline, sceneChangeKey, selectionFromScriptReference, selectionTargetKey, withOrigin };
4547
4846
  //# sourceMappingURL=index.js.map