@lingjingai/script-editor 0.1.16 → 0.1.17

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
@@ -1360,11 +1360,12 @@ function buildLocationDistribution(data, locationId) {
1360
1360
  (episode.scenes ?? []).forEach((scene, si) => {
1361
1361
  const inIds = (scene.location_ids ?? []).some((i) => (i ?? "").trim() === locationId);
1362
1362
  const ctx = getSceneContext(scene);
1363
- const inCtx = (ctx.locations ?? []).some((i) => (i.location_id ?? "").trim() === locationId);
1364
- if (inIds || inCtx) {
1363
+ const ctxRef = (ctx.locations ?? []).find((i) => (i.location_id ?? "").trim() === locationId);
1364
+ if (inIds || ctxRef) {
1365
1365
  items.push({
1366
1366
  key: `location-${locationId}-${ei}-${si}`,
1367
- ...refFields(episode, ei, scene, si)
1367
+ ...refFields(episode, ei, scene, si),
1368
+ stateId: ctxRef?.state_id
1368
1369
  });
1369
1370
  }
1370
1371
  });
@@ -1378,13 +1379,15 @@ function buildPropDistribution(data, propId, propName) {
1378
1379
  (data.episodes ?? []).forEach((episode, ei) => {
1379
1380
  (episode.scenes ?? []).forEach((scene, si) => {
1380
1381
  const ctx = getSceneContext(scene);
1381
- const byRef = (ctx.props ?? []).some((i) => (i.prop_id ?? "").trim() === id);
1382
+ const ctxRef = (ctx.props ?? []).find((i) => (i.prop_id ?? "").trim() === id);
1383
+ const byRef = !!ctxRef;
1382
1384
  const byId = !byRef && id ? (scene.actions ?? []).some((i) => (i.content ?? "").includes(id)) : false;
1383
1385
  const byName = !byRef && !byId && needle ? (scene.actions ?? []).some((i) => (i.content ?? "").toLowerCase().includes(needle)) : false;
1384
1386
  if (byRef || byId || byName) {
1385
1387
  items.push({
1386
1388
  key: `prop-${id || needle}-${ei}-${si}`,
1387
- ...refFields(episode, ei, scene, si)
1389
+ ...refFields(episode, ei, scene, si),
1390
+ stateId: ctxRef?.state_id
1388
1391
  });
1389
1392
  }
1390
1393
  });
@@ -2893,33 +2896,238 @@ function DeleteConfirmButton({
2893
2896
  }
2894
2897
  );
2895
2898
  }
2896
- function AssetReferenceGrid({
2899
+ var SPACE_ZH = { interior: "\u5185", exterior: "\u5916" };
2900
+ var TIME_ZH = { day: "\u65E5", night: "\u591C", dawn: "\u6668", dusk: "\u66AE" };
2901
+ function resolveCueId(item, actorMap, speakerMap) {
2902
+ const actorId = item.actor_id?.trim();
2903
+ if (actorId) return actorId;
2904
+ const spkId = item.speaker_id?.trim() || item.speakers?.[0]?.speaker_id?.trim() || "";
2905
+ if (!spkId) return "";
2906
+ const spk = speakerMap.get(spkId);
2907
+ const kind = spk?.source_kind?.trim();
2908
+ if ((kind === "actor" || kind === "prop") && spk?.source_id?.trim()) return spk.source_id.trim();
2909
+ if (spkId.startsWith("spk_") && actorMap.has(spkId.slice(4))) return spkId.slice(4);
2910
+ return "";
2911
+ }
2912
+ function ScenePreviewCard({
2913
+ data,
2914
+ anchor,
2915
+ highlightId,
2916
+ highlightKind,
2917
+ onEnter,
2918
+ onLeave
2919
+ }) {
2920
+ const actorMap = useMemo(() => getActorMap(data.actors), [data.actors]);
2921
+ const locationMap = useMemo(() => getLocationMap(data.locations), [data.locations]);
2922
+ const speakerMap = useMemo(() => getSpeakerMap(data.speakers), [data.speakers]);
2923
+ const episode = data.episodes?.[anchor.episodeIndex];
2924
+ const scene = episode?.scenes?.[anchor.sceneIndex];
2925
+ if (!scene) return null;
2926
+ const locId = getPrimarySceneLocationId(scene);
2927
+ const locName = (locId ? getLocationDisplayName(locationMap.get(locId)) : "") || "\u672A\u6307\u5B9A\u5730\u70B9";
2928
+ const env = scene.environment ?? {};
2929
+ const slug = `${SPACE_ZH[env.space ?? "interior"] ?? env.space ?? ""}\xB7${TIME_ZH[env.time ?? "day"] ?? env.time ?? ""}`;
2930
+ const actions = scene.actions ?? [];
2931
+ const W = 360;
2932
+ const vw = typeof window !== "undefined" ? window.innerWidth : 1280;
2933
+ const vh = typeof window !== "undefined" ? window.innerHeight : 800;
2934
+ const left = Math.min(Math.max(8, anchor.rect.left + anchor.rect.width / 2 - W / 2), vw - W - 8);
2935
+ const above = anchor.rect.top > vh * 0.55;
2936
+ const pos = above ? { left, bottom: vh - anchor.rect.top + 8, maxHeight: anchor.rect.top - 16 } : { left, top: anchor.rect.bottom + 8, maxHeight: vh - anchor.rect.bottom - 16 };
2937
+ const card = /* @__PURE__ */ jsxs("div", { className: "lj-se-spv", style: { width: W, ...pos }, onMouseEnter: onEnter, onMouseLeave: onLeave, children: [
2938
+ /* @__PURE__ */ jsxs("div", { className: "lj-se-spv-head", children: [
2939
+ /* @__PURE__ */ jsxs("span", { className: "lj-se-spv-num", children: [
2940
+ getEpisodeNumberLabel(episode, anchor.episodeIndex),
2941
+ " \xB7 ",
2942
+ getSceneNumberLabel(scene, anchor.sceneIndex)
2943
+ ] }),
2944
+ /* @__PURE__ */ jsxs("span", { className: "lj-se-spv-slug", children: [
2945
+ locName,
2946
+ " ",
2947
+ /* @__PURE__ */ jsx("span", { className: "lj-se-spv-slug-env", children: slug })
2948
+ ] })
2949
+ ] }),
2950
+ /* @__PURE__ */ jsx("div", { className: "lj-se-spv-body", children: actions.length === 0 ? /* @__PURE__ */ jsx("p", { className: "lj-se-spv-empty", children: "\uFF08\u672C\u573A\u6682\u65E0\u5185\u5BB9\uFF09" }) : actions.map((item, i) => /* @__PURE__ */ jsx(
2951
+ PreviewLine,
2952
+ {
2953
+ item,
2954
+ actorMap,
2955
+ speakerMap,
2956
+ highlightId,
2957
+ highlightKind
2958
+ },
2959
+ i
2960
+ )) })
2961
+ ] });
2962
+ return createPortal(card, anchor.host ?? document.body);
2963
+ }
2964
+ function PreviewLine({
2965
+ item,
2966
+ actorMap,
2967
+ speakerMap,
2968
+ highlightId,
2969
+ highlightKind
2970
+ }) {
2971
+ const type = item.type ?? "dialogue" /* Dialogue */;
2972
+ const content = item.content?.trim() ?? "";
2973
+ if (type === "action" /* Action */) {
2974
+ return /* @__PURE__ */ jsxs("p", { className: "lj-se-spv-action", children: [
2975
+ /* @__PURE__ */ jsx("span", { className: "lj-se-spv-mark", children: "\u25B3" }),
2976
+ content || "\u2026"
2977
+ ] });
2978
+ }
2979
+ if (type === "narration" /* Narration */) {
2980
+ return /* @__PURE__ */ jsx("p", { className: "lj-se-spv-narr", children: content || "\u2026" });
2981
+ }
2982
+ const isInner = type === "inner_thought" /* InnerThought */;
2983
+ const tone = isInner ? "inner" : "dialogue";
2984
+ const cueId = resolveCueId(item, actorMap, speakerMap);
2985
+ const speaker = getDialogueSpeakerLabel(item, actorMap, speakerMap) || "\u672A\u77E5\u89D2\u8272";
2986
+ const emotion = translateEmotionLabel(item.emotion);
2987
+ const delivery = getDialogueDeliveryLabel(item.delivery);
2988
+ const tag = isInner ? emotion ? `OS\xB7${emotion}` : "OS" : emotion;
2989
+ const mine = !!highlightId && (highlightKind === "actor" || highlightKind === "prop") && cueId === highlightId;
2990
+ const mineStyle = mine ? { "--mine": getActorCueVar(cueId, tone) } : void 0;
2991
+ return /* @__PURE__ */ jsxs("p", { className: cn("lj-se-spv-cue", isInner && "is-inner", mine && "is-mine"), style: mineStyle, children: [
2992
+ /* @__PURE__ */ jsxs("strong", { className: "lj-se-spv-speaker", style: { color: getActorCueVar(cueId, tone) }, children: [
2993
+ speaker,
2994
+ delivery ? /* @__PURE__ */ jsxs("span", { className: "lj-se-spv-delivery", children: [
2995
+ "\uFF08",
2996
+ delivery,
2997
+ "\uFF09"
2998
+ ] }) : null
2999
+ ] }),
3000
+ tag ? /* @__PURE__ */ jsxs("span", { className: "lj-se-spv-tag", children: [
3001
+ "\uFF08",
3002
+ tag,
3003
+ "\uFF09"
3004
+ ] }) : null,
3005
+ /* @__PURE__ */ jsx("span", { className: "lj-se-spv-colon", children: "\uFF1A" }),
3006
+ content || "\u2026"
3007
+ ] });
3008
+ }
3009
+ var UNASSIGNED = "__unassigned__";
3010
+ var CUE_COUNT = 8;
3011
+ var COL = 32;
3012
+ var MIN_EP_WIDTH = 48;
3013
+ function AssetStateTimeline({
3014
+ data,
3015
+ assetId,
3016
+ assetKind,
2897
3017
  refs,
2898
- highlight,
3018
+ states: states2,
2899
3019
  onJump
2900
3020
  }) {
2901
- if (refs.length === 0) return null;
2902
- return /* @__PURE__ */ jsx("div", { className: "lj-se-refgrid", children: refs.map(({ episodeIndex, episodeNumber, items }) => /* @__PURE__ */ jsxs("span", { className: "lj-se-refgrid-chunk", children: [
2903
- /* @__PURE__ */ jsxs("span", { className: "lj-se-refgrid-ep", children: [
2904
- "\u7B2C ",
2905
- String(episodeNumber).padStart(2, "0"),
2906
- " \u96C6"
3021
+ const [hover, setHover] = useState(null);
3022
+ const closeTimer = useRef(null);
3023
+ const cancelClose = () => {
3024
+ if (closeTimer.current) {
3025
+ clearTimeout(closeTimer.current);
3026
+ closeTimer.current = null;
3027
+ }
3028
+ };
3029
+ const scheduleClose = () => {
3030
+ cancelClose();
3031
+ closeTimer.current = setTimeout(() => setHover(null), 140);
3032
+ };
3033
+ const cols = [];
3034
+ for (const group of refs) for (const item of group.items) cols.push(item);
3035
+ const n = cols.length;
3036
+ if (n === 0) return null;
3037
+ const definedIds = states2.map((s) => (s.state_id ?? "").trim()).filter(Boolean);
3038
+ const lanes = states2.map((s, i) => ({ id: (s.state_id ?? "").trim(), name: getStateDisplayName(s) || "\u672A\u547D\u540D\u72B6\u6001", cueIndex: i % CUE_COUNT + 1 })).filter((l) => l.id);
3039
+ const isUnassigned = (c) => {
3040
+ const sid = (c.stateId ?? "").trim();
3041
+ return !sid || !definedIds.includes(sid);
3042
+ };
3043
+ if (cols.some(isUnassigned)) {
3044
+ lanes.push({ id: UNASSIGNED, name: lanes.length > 0 ? "\u672A\u6807\u6CE8" : "\u51FA\u73B0", cueIndex: 0 });
3045
+ }
3046
+ const colMetas = [];
3047
+ const epHeads = [];
3048
+ let x = 0;
3049
+ for (const group of refs) {
3050
+ const count = group.items.length;
3051
+ const w = Math.max(COL, Math.ceil(MIN_EP_WIDTH / count));
3052
+ epHeads.push({ episodeIndex: group.episodeIndex, episodeNumber: group.episodeNumber, width: count * w });
3053
+ for (const item of group.items) {
3054
+ colMetas.push({ item, left: x, width: w });
3055
+ x += w;
3056
+ }
3057
+ }
3058
+ const trackWidth = x;
3059
+ const center = (m) => m.left + m.width / 2;
3060
+ const cueVar = (cueIndex) => cueIndex === 0 ? "var(--lj-se-muted-fg)" : `var(--lj-se-actor-cue-${cueIndex})`;
3061
+ const laneMetas = (laneId) => colMetas.filter((m) => laneId === UNASSIGNED ? isUnassigned(m.item) : (m.item.stateId ?? "").trim() === laneId);
3062
+ return /* @__PURE__ */ jsxs("div", { className: "lj-se-sttl", children: [
3063
+ /* @__PURE__ */ jsxs("div", { className: "lj-se-sttl-axis", children: [
3064
+ /* @__PURE__ */ jsx("span", { className: "lj-se-sttl-axis-lab", children: "\u72B6\u6001 \uFF3C \u573A" }),
3065
+ /* @__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: [
3066
+ "\u7B2C",
3067
+ String(g.episodeNumber).padStart(2, "0"),
3068
+ "\u96C6"
3069
+ ] }, g.episodeIndex)) })
2907
3070
  ] }),
2908
- /* @__PURE__ */ jsx("span", { className: "lj-se-refgrid-scenes", children: items.map((ref) => {
2909
- const isCurrent = !!highlight && highlight.episodeIdx === ref.episodeIndex && highlight.sceneIdx === ref.sceneIndex;
2910
- return /* @__PURE__ */ jsx(
2911
- "button",
2912
- {
2913
- type: "button",
2914
- className: cn("lj-se-refgrid-scene", isCurrent && "is-current"),
2915
- title: ref.label,
2916
- onClick: () => onJump(ref.episodeIndex, ref.sceneIndex),
2917
- children: ref.sceneNumber
2918
- },
2919
- ref.key
2920
- );
2921
- }) })
2922
- ] }, episodeIndex)) });
3071
+ lanes.map((lane) => {
3072
+ const mine = laneMetas(lane.id);
3073
+ return /* @__PURE__ */ jsxs("div", { className: "lj-se-sttl-lane", children: [
3074
+ /* @__PURE__ */ jsxs("span", { className: "lj-se-sttl-lane-head", title: lane.name, children: [
3075
+ /* @__PURE__ */ jsx("span", { className: "lj-se-sttl-dot", style: { background: cueVar(lane.cueIndex) } }),
3076
+ /* @__PURE__ */ jsx("span", { className: "lj-se-sttl-lane-name", children: lane.name }),
3077
+ /* @__PURE__ */ jsx("span", { className: "lj-se-sttl-lane-count", children: mine.length })
3078
+ ] }),
3079
+ /* @__PURE__ */ jsxs("span", { className: "lj-se-sttl-track", style: { width: trackWidth, "--cue": cueVar(lane.cueIndex) }, children: [
3080
+ colMetas.map((m, i) => /* @__PURE__ */ jsx("span", { className: "lj-se-sttl-col", style: { left: m.left } }, `col-${i}`)),
3081
+ mine.map((m, k) => {
3082
+ const next = mine[k + 1];
3083
+ if (!next) return null;
3084
+ return /* @__PURE__ */ jsx(
3085
+ "span",
3086
+ {
3087
+ className: "lj-se-sttl-flow",
3088
+ style: { left: center(m), width: center(next) - center(m) }
3089
+ },
3090
+ `flow-${m.item.key}`
3091
+ );
3092
+ }),
3093
+ mine.map((m) => /* @__PURE__ */ jsx(
3094
+ "button",
3095
+ {
3096
+ type: "button",
3097
+ className: "lj-se-sttl-node",
3098
+ style: { left: center(m) },
3099
+ title: m.item.label,
3100
+ onClick: () => onJump(m.item.episodeIndex, m.item.sceneIndex),
3101
+ onMouseEnter: (e) => {
3102
+ cancelClose();
3103
+ const r = e.currentTarget.getBoundingClientRect();
3104
+ setHover({
3105
+ episodeIndex: m.item.episodeIndex,
3106
+ sceneIndex: m.item.sceneIndex,
3107
+ rect: { top: r.top, bottom: r.bottom, left: r.left, width: r.width },
3108
+ host: e.currentTarget.closest(".lj-se-root")
3109
+ });
3110
+ },
3111
+ onMouseLeave: scheduleClose,
3112
+ children: m.item.sceneNumber
3113
+ },
3114
+ m.item.key
3115
+ ))
3116
+ ] })
3117
+ ] }, lane.id);
3118
+ }),
3119
+ hover ? /* @__PURE__ */ jsx(
3120
+ ScenePreviewCard,
3121
+ {
3122
+ data,
3123
+ anchor: hover,
3124
+ highlightId: assetId,
3125
+ highlightKind: assetKind,
3126
+ onEnter: cancelClose,
3127
+ onLeave: scheduleClose
3128
+ }
3129
+ ) : null
3130
+ ] });
2923
3131
  }
2924
3132
  var META = {
2925
3133
  actors: { kind: "actor" },
@@ -3051,12 +3259,20 @@ function AssetCardImpl({
3051
3259
  ] }, sid);
3052
3260
  }) }) : null
3053
3261
  ] }),
3054
- refs.length > 0 ? /* @__PURE__ */ jsx(
3055
- AssetReferenceGrid,
3056
- {
3057
- refs,
3058
- onJump: (episodeIdx, sceneIdx) => onSelect({ kind: "scene", episodeIdx, sceneIdx, from: fromRef, origin: "sidebar" })
3059
- }
3262
+ refs.length > 0 ? (
3263
+ // state→scene reverse-lookup timeline: which state appears in which
3264
+ // scene, and how it flows. Stateless assets degrade to one "出现" lane.
3265
+ /* @__PURE__ */ jsx(
3266
+ AssetStateTimeline,
3267
+ {
3268
+ data,
3269
+ assetId,
3270
+ assetKind: kind,
3271
+ refs,
3272
+ states: states2,
3273
+ onJump: (episodeIdx, sceneIdx) => onSelect({ kind: "scene", episodeIdx, sceneIdx, from: fromRef, origin: "sidebar" })
3274
+ }
3275
+ )
3060
3276
  ) : null
3061
3277
  ] }) });
3062
3278
  }