@lingjingai/script-editor 0.1.15 → 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/INTEGRATION.md +2 -0
- package/dist/index.d.ts +9 -1
- package/dist/index.js +445 -202
- package/dist/index.js.map +1 -1
- package/dist/style.css +52 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -608,6 +608,169 @@ function EntityGroup({
|
|
|
608
608
|
)) : null
|
|
609
609
|
] });
|
|
610
610
|
}
|
|
611
|
+
var EditingSignalContext = createContext(null);
|
|
612
|
+
var LIVE_COMMIT_MS = 400;
|
|
613
|
+
function caretOffsetFromPoint(x, y) {
|
|
614
|
+
const d = document;
|
|
615
|
+
if (typeof d.caretRangeFromPoint === "function") {
|
|
616
|
+
const r = d.caretRangeFromPoint(x, y);
|
|
617
|
+
return r ? r.startOffset : null;
|
|
618
|
+
}
|
|
619
|
+
if (typeof d.caretPositionFromPoint === "function") {
|
|
620
|
+
const p = d.caretPositionFromPoint(x, y);
|
|
621
|
+
return p ? p.offset : null;
|
|
622
|
+
}
|
|
623
|
+
return null;
|
|
624
|
+
}
|
|
625
|
+
function EditableText({
|
|
626
|
+
value,
|
|
627
|
+
onSave,
|
|
628
|
+
placeholder,
|
|
629
|
+
multiline = false,
|
|
630
|
+
disabled = false,
|
|
631
|
+
className,
|
|
632
|
+
autoEdit = false,
|
|
633
|
+
onEditEnd,
|
|
634
|
+
spanProps
|
|
635
|
+
}) {
|
|
636
|
+
const [editing, setEditing] = useState(autoEdit);
|
|
637
|
+
const [draft, setDraft] = useState(value);
|
|
638
|
+
const caretRef = useRef(null);
|
|
639
|
+
const inputRef = useRef(null);
|
|
640
|
+
const committedRef = useRef(value);
|
|
641
|
+
const editingSignal = useContext(EditingSignalContext);
|
|
642
|
+
const liveTimerRef = useRef(null);
|
|
643
|
+
const composingRef = useRef(false);
|
|
644
|
+
const draftRef = useRef(value);
|
|
645
|
+
const onSaveRef = useRef(onSave);
|
|
646
|
+
onSaveRef.current = onSave;
|
|
647
|
+
const clearLiveTimer = () => {
|
|
648
|
+
if (liveTimerRef.current) {
|
|
649
|
+
clearTimeout(liveTimerRef.current);
|
|
650
|
+
liveTimerRef.current = null;
|
|
651
|
+
}
|
|
652
|
+
};
|
|
653
|
+
const flushLive = () => {
|
|
654
|
+
clearLiveTimer();
|
|
655
|
+
const next = draftRef.current;
|
|
656
|
+
if (next !== committedRef.current) {
|
|
657
|
+
committedRef.current = next;
|
|
658
|
+
onSaveRef.current(next);
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
const scheduleLive = () => {
|
|
662
|
+
clearLiveTimer();
|
|
663
|
+
liveTimerRef.current = setTimeout(() => {
|
|
664
|
+
liveTimerRef.current = null;
|
|
665
|
+
if (composingRef.current) return;
|
|
666
|
+
flushLive();
|
|
667
|
+
}, LIVE_COMMIT_MS);
|
|
668
|
+
};
|
|
669
|
+
useEffect(() => () => flushLive(), []);
|
|
670
|
+
useEffect(() => {
|
|
671
|
+
if (!editing || !editingSignal) return;
|
|
672
|
+
editingSignal.inc();
|
|
673
|
+
return () => editingSignal.dec();
|
|
674
|
+
}, [editing, editingSignal]);
|
|
675
|
+
useEffect(() => {
|
|
676
|
+
if (value === committedRef.current) return;
|
|
677
|
+
const userDiverged = editing && draft !== committedRef.current;
|
|
678
|
+
if (userDiverged) return;
|
|
679
|
+
setDraft(value);
|
|
680
|
+
committedRef.current = value;
|
|
681
|
+
}, [value, editing, draft]);
|
|
682
|
+
useEffect(() => {
|
|
683
|
+
if (autoEdit) setEditing(true);
|
|
684
|
+
}, [autoEdit]);
|
|
685
|
+
useLayoutEffect(() => {
|
|
686
|
+
if (!editing) return;
|
|
687
|
+
const el = inputRef.current;
|
|
688
|
+
if (!el) return;
|
|
689
|
+
if (multiline) autoSize(el);
|
|
690
|
+
el.focus();
|
|
691
|
+
const caret = caretRef.current;
|
|
692
|
+
const pos = caret == null ? el.value.length : Math.min(caret, el.value.length);
|
|
693
|
+
try {
|
|
694
|
+
el.setSelectionRange(pos, pos);
|
|
695
|
+
} catch {
|
|
696
|
+
}
|
|
697
|
+
caretRef.current = null;
|
|
698
|
+
}, [editing, multiline]);
|
|
699
|
+
function beginEdit(e) {
|
|
700
|
+
if (disabled) return;
|
|
701
|
+
const sel = window.getSelection();
|
|
702
|
+
if (sel && !sel.isCollapsed) return;
|
|
703
|
+
caretRef.current = caretOffsetFromPoint(e.clientX, e.clientY);
|
|
704
|
+
setDraft(committedRef.current);
|
|
705
|
+
draftRef.current = committedRef.current;
|
|
706
|
+
setEditing(true);
|
|
707
|
+
}
|
|
708
|
+
function commit() {
|
|
709
|
+
clearLiveTimer();
|
|
710
|
+
setEditing(false);
|
|
711
|
+
onEditEnd?.();
|
|
712
|
+
if (draft !== committedRef.current) {
|
|
713
|
+
committedRef.current = draft;
|
|
714
|
+
onSave(draft);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
function cancel() {
|
|
718
|
+
clearLiveTimer();
|
|
719
|
+
setDraft(committedRef.current);
|
|
720
|
+
draftRef.current = committedRef.current;
|
|
721
|
+
setEditing(false);
|
|
722
|
+
onEditEnd?.();
|
|
723
|
+
}
|
|
724
|
+
if (!editing) {
|
|
725
|
+
return /* @__PURE__ */ jsx(
|
|
726
|
+
"span",
|
|
727
|
+
{
|
|
728
|
+
...spanProps,
|
|
729
|
+
className: "lj-se-editable" + (className ? " " + className : "") + (disabled ? " lj-se-editable--ro" : ""),
|
|
730
|
+
onClick: beginEdit,
|
|
731
|
+
children: value || /* @__PURE__ */ jsx("span", { className: "lj-se-ph", children: placeholder ?? "" })
|
|
732
|
+
}
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
const common = {
|
|
736
|
+
ref: inputRef,
|
|
737
|
+
value: draft,
|
|
738
|
+
className: "lj-se-editing" + (className ? " " + className : ""),
|
|
739
|
+
onChange: (e) => {
|
|
740
|
+
const v = e.target.value;
|
|
741
|
+
setDraft(v);
|
|
742
|
+
draftRef.current = v;
|
|
743
|
+
if (multiline) autoSize(e.target);
|
|
744
|
+
if (!composingRef.current) scheduleLive();
|
|
745
|
+
},
|
|
746
|
+
onCompositionStart: () => {
|
|
747
|
+
composingRef.current = true;
|
|
748
|
+
clearLiveTimer();
|
|
749
|
+
},
|
|
750
|
+
onCompositionEnd: (e) => {
|
|
751
|
+
composingRef.current = false;
|
|
752
|
+
draftRef.current = e.currentTarget.value;
|
|
753
|
+
scheduleLive();
|
|
754
|
+
},
|
|
755
|
+
onBlur: commit,
|
|
756
|
+
onKeyDown: (e) => {
|
|
757
|
+
if (e.key === "Escape") {
|
|
758
|
+
e.preventDefault();
|
|
759
|
+
cancel();
|
|
760
|
+
} else if (e.key === "Enter") {
|
|
761
|
+
if (!multiline || e.metaKey || e.ctrlKey) {
|
|
762
|
+
e.preventDefault();
|
|
763
|
+
commit();
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
};
|
|
768
|
+
return multiline ? /* @__PURE__ */ jsx("textarea", { ...common, rows: 1 }) : /* @__PURE__ */ jsx("input", { ...common, type: "text" });
|
|
769
|
+
}
|
|
770
|
+
function autoSize(el) {
|
|
771
|
+
el.style.height = "auto";
|
|
772
|
+
el.style.height = el.scrollHeight + "px";
|
|
773
|
+
}
|
|
611
774
|
function FormatToggle({
|
|
612
775
|
value,
|
|
613
776
|
onChange
|
|
@@ -628,22 +791,43 @@ function FormatToggle({
|
|
|
628
791
|
)) });
|
|
629
792
|
}
|
|
630
793
|
function CanvasTopBar({
|
|
794
|
+
title,
|
|
795
|
+
onTitleChange,
|
|
796
|
+
titleDisabled,
|
|
631
797
|
contextLabel,
|
|
632
798
|
format,
|
|
633
799
|
onFormatChange,
|
|
634
800
|
canUndo,
|
|
635
801
|
canRedo,
|
|
636
802
|
onUndo,
|
|
637
|
-
onRedo
|
|
803
|
+
onRedo,
|
|
804
|
+
actions
|
|
638
805
|
}) {
|
|
806
|
+
const hasTitle = onTitleChange != null || (title ?? "").length > 0;
|
|
639
807
|
return /* @__PURE__ */ jsxs("header", { className: "lj-se-topbar", children: [
|
|
640
|
-
/* @__PURE__ */
|
|
808
|
+
/* @__PURE__ */ jsxs("div", { className: "lj-se-topbar-left", children: [
|
|
809
|
+
hasTitle ? onTitleChange ? /* @__PURE__ */ jsx(
|
|
810
|
+
EditableText,
|
|
811
|
+
{
|
|
812
|
+
className: "lj-se-topbar-title",
|
|
813
|
+
value: title ?? "",
|
|
814
|
+
placeholder: "\u672A\u547D\u540D\u5267\u672C",
|
|
815
|
+
disabled: titleDisabled,
|
|
816
|
+
onSave: onTitleChange
|
|
817
|
+
}
|
|
818
|
+
) : /* @__PURE__ */ jsx("span", { className: "lj-se-topbar-title lj-se-topbar-title--ro", children: title }) : null,
|
|
819
|
+
contextLabel ? /* @__PURE__ */ jsxs("span", { className: "lj-se-topbar-ctx", children: [
|
|
820
|
+
hasTitle ? /* @__PURE__ */ jsx("span", { className: "lj-se-topbar-sep", "aria-hidden": true, children: "/" }) : null,
|
|
821
|
+
contextLabel
|
|
822
|
+
] }) : null
|
|
823
|
+
] }),
|
|
641
824
|
/* @__PURE__ */ jsxs("div", { className: "lj-se-topbar-right", children: [
|
|
642
825
|
/* @__PURE__ */ jsx(FormatToggle, { value: format, onChange: onFormatChange }),
|
|
643
826
|
onUndo || onRedo ? /* @__PURE__ */ jsxs("div", { className: "lj-se-topbar-undo", children: [
|
|
644
827
|
/* @__PURE__ */ jsx("button", { type: "button", className: "lj-se-rowbtn", disabled: !canUndo, title: "\u64A4\u9500", onClick: onUndo, children: /* @__PURE__ */ jsx(Undo2, { className: "lj-se-ic-4", strokeWidth: 1.75 }) }),
|
|
645
828
|
/* @__PURE__ */ jsx("button", { type: "button", className: "lj-se-rowbtn", disabled: !canRedo, title: "\u91CD\u505A", onClick: onRedo, children: /* @__PURE__ */ jsx(Redo2, { className: "lj-se-ic-4", strokeWidth: 1.75 }) })
|
|
646
|
-
] }) : null
|
|
829
|
+
] }) : null,
|
|
830
|
+
actions ? /* @__PURE__ */ jsx("div", { className: "lj-se-topbar-actions", children: actions }) : null
|
|
647
831
|
] })
|
|
648
832
|
] });
|
|
649
833
|
}
|
|
@@ -1176,11 +1360,12 @@ function buildLocationDistribution(data, locationId) {
|
|
|
1176
1360
|
(episode.scenes ?? []).forEach((scene, si) => {
|
|
1177
1361
|
const inIds = (scene.location_ids ?? []).some((i) => (i ?? "").trim() === locationId);
|
|
1178
1362
|
const ctx = getSceneContext(scene);
|
|
1179
|
-
const
|
|
1180
|
-
if (inIds ||
|
|
1363
|
+
const ctxRef = (ctx.locations ?? []).find((i) => (i.location_id ?? "").trim() === locationId);
|
|
1364
|
+
if (inIds || ctxRef) {
|
|
1181
1365
|
items.push({
|
|
1182
1366
|
key: `location-${locationId}-${ei}-${si}`,
|
|
1183
|
-
...refFields(episode, ei, scene, si)
|
|
1367
|
+
...refFields(episode, ei, scene, si),
|
|
1368
|
+
stateId: ctxRef?.state_id
|
|
1184
1369
|
});
|
|
1185
1370
|
}
|
|
1186
1371
|
});
|
|
@@ -1194,13 +1379,15 @@ function buildPropDistribution(data, propId, propName) {
|
|
|
1194
1379
|
(data.episodes ?? []).forEach((episode, ei) => {
|
|
1195
1380
|
(episode.scenes ?? []).forEach((scene, si) => {
|
|
1196
1381
|
const ctx = getSceneContext(scene);
|
|
1197
|
-
const
|
|
1382
|
+
const ctxRef = (ctx.props ?? []).find((i) => (i.prop_id ?? "").trim() === id);
|
|
1383
|
+
const byRef = !!ctxRef;
|
|
1198
1384
|
const byId = !byRef && id ? (scene.actions ?? []).some((i) => (i.content ?? "").includes(id)) : false;
|
|
1199
1385
|
const byName = !byRef && !byId && needle ? (scene.actions ?? []).some((i) => (i.content ?? "").toLowerCase().includes(needle)) : false;
|
|
1200
1386
|
if (byRef || byId || byName) {
|
|
1201
1387
|
items.push({
|
|
1202
1388
|
key: `prop-${id || needle}-${ei}-${si}`,
|
|
1203
|
-
...refFields(episode, ei, scene, si)
|
|
1389
|
+
...refFields(episode, ei, scene, si),
|
|
1390
|
+
stateId: ctxRef?.state_id
|
|
1204
1391
|
});
|
|
1205
1392
|
}
|
|
1206
1393
|
});
|
|
@@ -2014,169 +2201,6 @@ function getActorCueVar(actorId, tone = "dialogue") {
|
|
|
2014
2201
|
const idx = hashActorId(id) % PALETTE_SIZE + 1;
|
|
2015
2202
|
return `var(--lj-se-actor-cue-${idx})`;
|
|
2016
2203
|
}
|
|
2017
|
-
var EditingSignalContext = createContext(null);
|
|
2018
|
-
var LIVE_COMMIT_MS = 400;
|
|
2019
|
-
function caretOffsetFromPoint(x, y) {
|
|
2020
|
-
const d = document;
|
|
2021
|
-
if (typeof d.caretRangeFromPoint === "function") {
|
|
2022
|
-
const r = d.caretRangeFromPoint(x, y);
|
|
2023
|
-
return r ? r.startOffset : null;
|
|
2024
|
-
}
|
|
2025
|
-
if (typeof d.caretPositionFromPoint === "function") {
|
|
2026
|
-
const p = d.caretPositionFromPoint(x, y);
|
|
2027
|
-
return p ? p.offset : null;
|
|
2028
|
-
}
|
|
2029
|
-
return null;
|
|
2030
|
-
}
|
|
2031
|
-
function EditableText({
|
|
2032
|
-
value,
|
|
2033
|
-
onSave,
|
|
2034
|
-
placeholder,
|
|
2035
|
-
multiline = false,
|
|
2036
|
-
disabled = false,
|
|
2037
|
-
className,
|
|
2038
|
-
autoEdit = false,
|
|
2039
|
-
onEditEnd,
|
|
2040
|
-
spanProps
|
|
2041
|
-
}) {
|
|
2042
|
-
const [editing, setEditing] = useState(autoEdit);
|
|
2043
|
-
const [draft, setDraft] = useState(value);
|
|
2044
|
-
const caretRef = useRef(null);
|
|
2045
|
-
const inputRef = useRef(null);
|
|
2046
|
-
const committedRef = useRef(value);
|
|
2047
|
-
const editingSignal = useContext(EditingSignalContext);
|
|
2048
|
-
const liveTimerRef = useRef(null);
|
|
2049
|
-
const composingRef = useRef(false);
|
|
2050
|
-
const draftRef = useRef(value);
|
|
2051
|
-
const onSaveRef = useRef(onSave);
|
|
2052
|
-
onSaveRef.current = onSave;
|
|
2053
|
-
const clearLiveTimer = () => {
|
|
2054
|
-
if (liveTimerRef.current) {
|
|
2055
|
-
clearTimeout(liveTimerRef.current);
|
|
2056
|
-
liveTimerRef.current = null;
|
|
2057
|
-
}
|
|
2058
|
-
};
|
|
2059
|
-
const flushLive = () => {
|
|
2060
|
-
clearLiveTimer();
|
|
2061
|
-
const next = draftRef.current;
|
|
2062
|
-
if (next !== committedRef.current) {
|
|
2063
|
-
committedRef.current = next;
|
|
2064
|
-
onSaveRef.current(next);
|
|
2065
|
-
}
|
|
2066
|
-
};
|
|
2067
|
-
const scheduleLive = () => {
|
|
2068
|
-
clearLiveTimer();
|
|
2069
|
-
liveTimerRef.current = setTimeout(() => {
|
|
2070
|
-
liveTimerRef.current = null;
|
|
2071
|
-
if (composingRef.current) return;
|
|
2072
|
-
flushLive();
|
|
2073
|
-
}, LIVE_COMMIT_MS);
|
|
2074
|
-
};
|
|
2075
|
-
useEffect(() => () => flushLive(), []);
|
|
2076
|
-
useEffect(() => {
|
|
2077
|
-
if (!editing || !editingSignal) return;
|
|
2078
|
-
editingSignal.inc();
|
|
2079
|
-
return () => editingSignal.dec();
|
|
2080
|
-
}, [editing, editingSignal]);
|
|
2081
|
-
useEffect(() => {
|
|
2082
|
-
if (value === committedRef.current) return;
|
|
2083
|
-
const userDiverged = editing && draft !== committedRef.current;
|
|
2084
|
-
if (userDiverged) return;
|
|
2085
|
-
setDraft(value);
|
|
2086
|
-
committedRef.current = value;
|
|
2087
|
-
}, [value, editing, draft]);
|
|
2088
|
-
useEffect(() => {
|
|
2089
|
-
if (autoEdit) setEditing(true);
|
|
2090
|
-
}, [autoEdit]);
|
|
2091
|
-
useLayoutEffect(() => {
|
|
2092
|
-
if (!editing) return;
|
|
2093
|
-
const el = inputRef.current;
|
|
2094
|
-
if (!el) return;
|
|
2095
|
-
if (multiline) autoSize(el);
|
|
2096
|
-
el.focus();
|
|
2097
|
-
const caret = caretRef.current;
|
|
2098
|
-
const pos = caret == null ? el.value.length : Math.min(caret, el.value.length);
|
|
2099
|
-
try {
|
|
2100
|
-
el.setSelectionRange(pos, pos);
|
|
2101
|
-
} catch {
|
|
2102
|
-
}
|
|
2103
|
-
caretRef.current = null;
|
|
2104
|
-
}, [editing, multiline]);
|
|
2105
|
-
function beginEdit(e) {
|
|
2106
|
-
if (disabled) return;
|
|
2107
|
-
const sel = window.getSelection();
|
|
2108
|
-
if (sel && !sel.isCollapsed) return;
|
|
2109
|
-
caretRef.current = caretOffsetFromPoint(e.clientX, e.clientY);
|
|
2110
|
-
setDraft(committedRef.current);
|
|
2111
|
-
draftRef.current = committedRef.current;
|
|
2112
|
-
setEditing(true);
|
|
2113
|
-
}
|
|
2114
|
-
function commit() {
|
|
2115
|
-
clearLiveTimer();
|
|
2116
|
-
setEditing(false);
|
|
2117
|
-
onEditEnd?.();
|
|
2118
|
-
if (draft !== committedRef.current) {
|
|
2119
|
-
committedRef.current = draft;
|
|
2120
|
-
onSave(draft);
|
|
2121
|
-
}
|
|
2122
|
-
}
|
|
2123
|
-
function cancel() {
|
|
2124
|
-
clearLiveTimer();
|
|
2125
|
-
setDraft(committedRef.current);
|
|
2126
|
-
draftRef.current = committedRef.current;
|
|
2127
|
-
setEditing(false);
|
|
2128
|
-
onEditEnd?.();
|
|
2129
|
-
}
|
|
2130
|
-
if (!editing) {
|
|
2131
|
-
return /* @__PURE__ */ jsx(
|
|
2132
|
-
"span",
|
|
2133
|
-
{
|
|
2134
|
-
...spanProps,
|
|
2135
|
-
className: "lj-se-editable" + (className ? " " + className : "") + (disabled ? " lj-se-editable--ro" : ""),
|
|
2136
|
-
onClick: beginEdit,
|
|
2137
|
-
children: value || /* @__PURE__ */ jsx("span", { className: "lj-se-ph", children: placeholder ?? "" })
|
|
2138
|
-
}
|
|
2139
|
-
);
|
|
2140
|
-
}
|
|
2141
|
-
const common = {
|
|
2142
|
-
ref: inputRef,
|
|
2143
|
-
value: draft,
|
|
2144
|
-
className: "lj-se-editing" + (className ? " " + className : ""),
|
|
2145
|
-
onChange: (e) => {
|
|
2146
|
-
const v = e.target.value;
|
|
2147
|
-
setDraft(v);
|
|
2148
|
-
draftRef.current = v;
|
|
2149
|
-
if (multiline) autoSize(e.target);
|
|
2150
|
-
if (!composingRef.current) scheduleLive();
|
|
2151
|
-
},
|
|
2152
|
-
onCompositionStart: () => {
|
|
2153
|
-
composingRef.current = true;
|
|
2154
|
-
clearLiveTimer();
|
|
2155
|
-
},
|
|
2156
|
-
onCompositionEnd: (e) => {
|
|
2157
|
-
composingRef.current = false;
|
|
2158
|
-
draftRef.current = e.currentTarget.value;
|
|
2159
|
-
scheduleLive();
|
|
2160
|
-
},
|
|
2161
|
-
onBlur: commit,
|
|
2162
|
-
onKeyDown: (e) => {
|
|
2163
|
-
if (e.key === "Escape") {
|
|
2164
|
-
e.preventDefault();
|
|
2165
|
-
cancel();
|
|
2166
|
-
} else if (e.key === "Enter") {
|
|
2167
|
-
if (!multiline || e.metaKey || e.ctrlKey) {
|
|
2168
|
-
e.preventDefault();
|
|
2169
|
-
commit();
|
|
2170
|
-
}
|
|
2171
|
-
}
|
|
2172
|
-
}
|
|
2173
|
-
};
|
|
2174
|
-
return multiline ? /* @__PURE__ */ jsx("textarea", { ...common, rows: 1 }) : /* @__PURE__ */ jsx("input", { ...common, type: "text" });
|
|
2175
|
-
}
|
|
2176
|
-
function autoSize(el) {
|
|
2177
|
-
el.style.height = "auto";
|
|
2178
|
-
el.style.height = el.scrollHeight + "px";
|
|
2179
|
-
}
|
|
2180
2204
|
function formatStateChangeLabels(item, actorMap, locationMap, propMap) {
|
|
2181
2205
|
const labels = (item.state_changes ?? []).map((change) => {
|
|
2182
2206
|
const kind = change.target_kind?.trim() || "actor";
|
|
@@ -2872,33 +2896,238 @@ function DeleteConfirmButton({
|
|
|
2872
2896
|
}
|
|
2873
2897
|
);
|
|
2874
2898
|
}
|
|
2875
|
-
|
|
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,
|
|
2876
3017
|
refs,
|
|
2877
|
-
|
|
3018
|
+
states: states2,
|
|
2878
3019
|
onJump
|
|
2879
3020
|
}) {
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
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)) })
|
|
2886
3070
|
] }),
|
|
2887
|
-
|
|
2888
|
-
const
|
|
2889
|
-
return /* @__PURE__ */
|
|
2890
|
-
"
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
className:
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
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
|
+
] });
|
|
2902
3131
|
}
|
|
2903
3132
|
var META = {
|
|
2904
3133
|
actors: { kind: "actor" },
|
|
@@ -3030,12 +3259,20 @@ function AssetCardImpl({
|
|
|
3030
3259
|
] }, sid);
|
|
3031
3260
|
}) }) : null
|
|
3032
3261
|
] }),
|
|
3033
|
-
refs.length > 0 ?
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
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
|
+
)
|
|
3039
3276
|
) : null
|
|
3040
3277
|
] }) });
|
|
3041
3278
|
}
|
|
@@ -3752,6 +3989,8 @@ function ScriptEditor({
|
|
|
3752
3989
|
disabled = false,
|
|
3753
3990
|
className,
|
|
3754
3991
|
dark,
|
|
3992
|
+
onTitleChange,
|
|
3993
|
+
topBarActions,
|
|
3755
3994
|
saveStatus = "idle",
|
|
3756
3995
|
canUndo,
|
|
3757
3996
|
canRedo,
|
|
@@ -3926,13 +4165,17 @@ function ScriptEditor({
|
|
|
3926
4165
|
/* @__PURE__ */ jsx(
|
|
3927
4166
|
CanvasTopBar,
|
|
3928
4167
|
{
|
|
4168
|
+
title: value.title,
|
|
4169
|
+
onTitleChange: disabled ? void 0 : onTitleChange,
|
|
4170
|
+
titleDisabled: disabled,
|
|
3929
4171
|
contextLabel,
|
|
3930
4172
|
format,
|
|
3931
4173
|
onFormatChange: setFormat,
|
|
3932
4174
|
canUndo,
|
|
3933
4175
|
canRedo,
|
|
3934
4176
|
onUndo,
|
|
3935
|
-
onRedo
|
|
4177
|
+
onRedo,
|
|
4178
|
+
actions: topBarActions
|
|
3936
4179
|
}
|
|
3937
4180
|
),
|
|
3938
4181
|
/* @__PURE__ */ jsxs("div", { className: "lj-se-canvas", ref: canvasRef, children: [
|
|
@@ -3990,7 +4233,7 @@ function buildContextLabel(value, selection) {
|
|
|
3990
4233
|
] });
|
|
3991
4234
|
}
|
|
3992
4235
|
}
|
|
3993
|
-
return
|
|
4236
|
+
return null;
|
|
3994
4237
|
}
|
|
3995
4238
|
function Ctx({ kicker, name }) {
|
|
3996
4239
|
return /* @__PURE__ */ jsxs("span", { className: "lj-se-ctx", children: [
|