lrc-maker
Version:
歌词滚动姬|可能是你所能见到的最好用的歌词制作工具
191 lines • 7.74 kB
JavaScript
import { convertTimeToTag, formatText } from "../lrc-parser.js";
import { audioRef, currentTimePubSub } from "../utils/audiomodule.js";
import { isKeyboardElement } from "../utils/is-keyboard-element.js";
import { appContext } from "./app.context.js";
import { AsidePanel } from "./asidepanel.js";
import { Curser } from "./curser.js";
const { useCallback, useContext, useEffect, useRef, useState } = React;
const SpaceButton = ({ sync }) => {
return (React.createElement("button", { className: "space-button", onClick: sync }, "space"));
};
export const Synchronizer = ({ state, dispatch }) => {
const self = useRef(Symbol(Synchronizer.name));
const { selectIndex, currentIndex: highlightIndex, lyric } = state;
const { prefState, lang } = useContext(appContext);
useEffect(() => {
dispatch({
type: 4,
payload: {
name: "tool",
value: `${lang.app.name} https://lrc-maker.github.io`,
},
});
}, [dispatch, lang]);
const [syncMode, setSyncMode] = useState(() => sessionStorage.getItem("sync-mode") === 1..toString() ? 1 : 0);
useEffect(() => {
sessionStorage.setItem("sync-mode", syncMode.toString());
}, [syncMode]);
const ul = useRef(null);
const needScrollLine = {
[0]: selectIndex,
[1]: highlightIndex,
}[syncMode];
useEffect(() => {
var _a;
const line = (_a = ul.current) === null || _a === void 0 ? void 0 : _a.children[needScrollLine];
if (line !== undefined) {
line.scrollIntoView({
behavior: "smooth",
block: "center",
inline: "center",
});
}
}, [needScrollLine]);
useEffect(() => {
return currentTimePubSub.sub(self.current, (time) => {
dispatch({ type: 1, payload: time });
});
}, [dispatch]);
const sync = useCallback(() => {
if (!audioRef.duration) {
return;
}
dispatch({
type: 2,
payload: audioRef.currentTime,
});
}, [dispatch]);
const adjust = useCallback((ev, offset, index) => {
var _a;
if (!audioRef.duration) {
return;
}
const selectTime = (_a = lyric[index]) === null || _a === void 0 ? void 0 : _a.time;
if (selectTime === undefined) {
return;
}
dispatch({
type: 3,
payload: audioRef.step(ev, offset, selectTime),
});
}, [dispatch, lyric]);
useEffect(() => {
const listener = (ev) => {
const { code, key, target } = ev;
const codeOrKey = code || key;
if (isKeyboardElement(target)) {
return;
}
if (codeOrKey === "Backspace" || codeOrKey === "Delete" || codeOrKey === "Del") {
ev.preventDefault();
dispatch({
type: 6,
payload: undefined,
});
return;
}
if (code === "Digit0" || key === "0") {
ev.preventDefault();
adjust(ev, 0, selectIndex);
return;
}
if (code === "Minus" || key === "-" || key === "_") {
ev.preventDefault();
adjust(ev, -0.5, selectIndex);
return;
}
if (code === "Equal" || key === "+" || key === "=") {
ev.preventDefault();
adjust(ev, 0.5, selectIndex);
return;
}
if (ev.metaKey === true || ev.ctrlKey === true) {
return;
}
if (code === "Space" || key === " " || key === "Spacebar") {
ev.preventDefault();
sync();
}
else if (["ArrowUp", "KeyW", "KeyJ", "Up", "W", "w", "J", "j"].includes(codeOrKey)) {
ev.preventDefault();
dispatch({ type: 5, payload: (index) => index - 1 });
}
else if (["ArrowDown", "KeyS", "KeyK", "Down", "S", "s", "K", "k"].includes(codeOrKey)) {
ev.preventDefault();
dispatch({ type: 5, payload: (index) => index + 1 });
}
else if (codeOrKey === "Home") {
ev.preventDefault();
dispatch({ type: 5, payload: () => 0 });
}
else if (codeOrKey === "End") {
ev.preventDefault();
dispatch({ type: 5, payload: () => Infinity });
}
else if (codeOrKey === "PageUp") {
ev.preventDefault();
dispatch({ type: 5, payload: (index) => index - 10 });
}
else if (codeOrKey === "PageDown") {
ev.preventDefault();
dispatch({ type: 5, payload: (index) => index + 10 });
}
};
document.addEventListener("keydown", listener);
return () => {
document.removeEventListener("keydown", listener);
};
}, [adjust, dispatch, selectIndex, sync]);
const onLineClick = useCallback((ev) => {
ev.stopPropagation();
const target = ev.target;
if (target.classList.contains("line")) {
const lineKey = Number.parseInt(target.dataset.key, 10) || 0;
dispatch({ type: 5, payload: () => lineKey });
}
}, [dispatch]);
const onLineDoubleClick = useCallback((ev) => {
ev.stopPropagation();
if (!audioRef.duration) {
return;
}
const target = ev.target;
if (target.classList.contains("line")) {
const key = Number.parseInt(target.dataset.key, 10);
adjust(ev, 0, key);
}
}, [adjust]);
const LyricLineIter = useCallback((line, index, lines) => {
const select = index === selectIndex;
const highlight = index === highlightIndex;
const error = index > 0 && lines[index].time <= lines[index - 1].time;
const className = Object.entries({
line: true,
select,
highlight,
error,
})
.reduce((p, [name, value]) => {
if (value) {
p.push(name);
}
return p;
}, [])
.join(" ");
return (React.createElement(LyricLine, { key: index, index: index, className: className, line: line, select: select, prefState: prefState }));
}, [selectIndex, highlightIndex, prefState]);
const ulClassName = prefState.screenButton ? "lyric-list on-screen-button" : "lyric-list";
return (React.createElement(React.Fragment, null,
React.createElement("ul", { ref: ul, className: ulClassName, onClickCapture: onLineClick, onDoubleClickCapture: onLineDoubleClick }, state.lyric.map(LyricLineIter)),
React.createElement(AsidePanel, { syncMode: syncMode, setSyncMode: setSyncMode, lrcDispatch: dispatch, prefState: prefState }),
prefState.screenButton && React.createElement(SpaceButton, { sync: sync })));
};
const LyricLine = ({ line, index, select, className, prefState }) => {
const lineTime = convertTimeToTag(line.time, prefState.fixed);
const lineText = formatText(line.text, prefState.spaceStart, prefState.spaceEnd);
return (React.createElement("li", { key: index, "data-key": index, className: className },
select && React.createElement(Curser, { fixed: prefState.fixed }),
React.createElement("time", { className: "line-time" }, lineTime),
React.createElement("span", { className: "line-text" }, lineText)));
};
//# sourceMappingURL=synchronizer.js.map