import { FolderOpenOutlined, HighlightOutlined, MenuUnfoldOutlined, PlusOutlined, RedoOutlined, SaveOutlined, UndoOutlined, } from "@ant-design/icons"; import { Menu } from "antd"; import state from "../store/state"; import store from "../store/global"; import { cloneNotation, createNotation, isNote, notations as N, } from "../util/notation"; import { cloneParagraph, createParagraph, createParagraphWithNotations, } from "../util/paragraph"; import { convertTone, convertToneTo } from "../util/tone-convert"; import { exportFile, loadFile, saveFile } from "../util/file"; import { findParagraphAndNotation, resetGlobalData } from "../util/editor"; import { go, runInWrappedAction, unwrappedAction, wrappedAction, } from "../store/history"; const { SubMenu } = Menu; const handleOpenFile = unwrappedAction((ev) => { const file = ev.target.files[0]; loadFile(file); }); const handleSaveFile = () => { saveFile(); }; const handleExportFile = unwrappedAction(() => { state.selectedNotationKey = null; exportFile(); }); const handleCreate = () => { resetGlobalData(); }; const handleEditMenu = wrappedAction(({ key }) => { switch (key) { case "undo": go(-1); break; case "redo": go(1); break; case "reset-title": runInWrappedAction(() => { store.title = "【歌曲名称】"; }); break; case "reset-authors": store.authors = [ "【作曲者】 作曲", "【填词者】 填词", "【记谱者】 记谱", ]; break; case "add-paragraph": { store.paragraphs.push(createParagraphWithNotations()); break; } default: break; } }); const handleConvertMenu = ({ key }) => { switch (true) { case key.startsWith("convertTo"): const destTone = key.split("-", 2)[1]; convertToneTo(destTone); break; case key === "convert-up": convertTone(2); break; case key === "convert-down": convertTone(-2); break; case key === "convert-up8": convertTone(12); break; case key === "convert-down8": convertTone(-12); break; default: break; } }; // 画布上的点击事件 const handleClick = wrappedAction((ev) => { if (state.shouldNotationBlurAfterClick && state.selectedNotationKey) { state.lastSelectedNotationKey = state.selectedNotationKey; state.selectedNotationKey = null; } state.shouldNotationBlurAfterClick = true; }); const handleKeyPress = wrappedAction((ev) => { const inputKey = ev.key.toLowerCase(); const shift = ev.shiftKey; const ctrl = ev.ctrlKey; if (state.selectedNotationKey) { // 仅选中符号时作用 if (state.helpDialogVisible || state.configDialogVisible) { return; } const { paragraph, notation, paragraphIndex, notationIndex, } = findParagraphAndNotation(state.selectedNotationKey); if (!notation) { return; } switch (true) { case isNote(inputKey) && !ctrl && !shift: notation.note = inputKey; break; case inputKey === "(" && !ctrl: notation.note = N.crackerOpen; break; case inputKey === ")" && !ctrl: notation.note = N.crackerClose; break; case inputKey === "-" && !ctrl && !shift: notation.note = N.extend; break; case inputKey === "|" && !ctrl: notation.note = N.separator; break; case inputKey === "u" && !ctrl && !shift: notation.underline += 1; break; case inputKey === "u" && !ctrl && shift: notation.underline -= 1; notation.underline = Math.max(0, notation.underline); break; case inputKey === "." && !ctrl && !shift: notation.dotted = !notation.dotted; break; case inputKey === "8" && !ctrl && !shift: notation.octave += 1; break; case inputKey === "*" && !ctrl: notation.octave -= 1; break; case inputKey === "#" && !ctrl: if (notation.prefixSups.indexOf("♯") !== 0) { if (notation.prefixSups[0] === "♭") { notation.prefixSups.shift(); } notation.prefixSups.unshift("♯"); } else { notation.prefixSups.shift(); } break; case inputKey === "b" && !ctrl && !shift: { if (notation.prefixSups.indexOf("♭") !== 0) { if (notation.prefixSups[0] === "♯") { notation.prefixSups.shift(); } notation.prefixSups.unshift("♭"); } else { notation.prefixSups.shift(); } break; } case inputKey === "~" && !ctrl: { if (notation.topDecorators.includes("~")) { notation.topDecorators.splice(notation.topDecorators.indexOf("~"), 1); } else { notation.topDecorators.push("~"); } break; } case inputKey === "enter" && !ctrl && !shift: { if (paragraph.notations[notationIndex + 1]) { state.selectedNotationKey = paragraph.notations[notationIndex + 1].key; } else { const nextNotation = createNotation(); paragraph.notations.push(nextNotation); state.selectedNotationKey = nextNotation.key; } break; } case inputKey === "l" && !ctrl && !shift: { const nextNotation = paragraph.notations[notationIndex + 1] || store.paragraphs[paragraphIndex + 1].notations?.at(0); if (nextNotation) { state.selectedNotationKey = nextNotation.key; } break; } case inputKey === "enter" && !ctrl && shift: { if (paragraph.notations[notationIndex - 1]) { state.selectedNotationKey = paragraph.notations[notationIndex - 1].key; } break; } case inputKey === "h" && !ctrl && !shift: { const prevNotation = paragraph.notations[notationIndex - 1] || store.paragraphs[paragraphIndex - 1].notations?.at(-1); if (prevNotation) { state.selectedNotationKey = prevNotation.key; } break; } case inputKey === "enter" && ctrl && !shift: { const newNotation = createNotation(); if ( notationIndex === paragraph.notations.length - 1 && paragraphIndex === store.paragraphs.length - 1 ) { // 在最后一个音符时ctrl+enter插入新段落并创建符号 store.paragraphs.push(createParagraph({ notations: [newNotation] })); state.selectedNotationKey = store.paragraphs .at(-1) .notations.at(0).key; } else { paragraph.notations.splice(notationIndex + 1, 0, newNotation); state.selectedNotationKey = newNotation.key; } break; } case inputKey === "j" && !ctrl && !shift: { const nextParagraph = store.paragraphs[paragraphIndex + 1]; if (nextParagraph?.notations?.length) { const nextNotation = nextParagraph.notations[notationIndex] || nextParagraph.notations.at(-1); state.selectedNotationKey = nextNotation.key; } break; } case inputKey === "k" && !ctrl && !shift: { const prevParagraph = store.paragraphs[paragraphIndex - 1]; if (prevParagraph?.notations?.length) { const prevNotation = prevParagraph.notations[notationIndex] || prevParagraph.notations.at(-1); state.selectedNotationKey = prevNotation.key; } break; } case inputKey === "backspace" && !ctrl && !shift: case inputKey === "delete" && !ctrl && !shift: { paragraph.notations.splice(notationIndex, 1); const currNotation = paragraph.notations[notationIndex] || paragraph.notations[notationIndex - 1]; state.selectedNotationKey = currNotation?.key; break; } case inputKey === "backspace" && !ctrl && shift: case inputKey === "delete" && !ctrl && shift: { store.paragraphs.splice(paragraphIndex, 1); const currParagraph = store.paragraphs[paragraphIndex] || store.paragraphs.at(-1); const currNotation = currParagraph?.notations?.at(notationIndex) || currParagraph?.notations?.at(-1); state.selectedNotationKey = currNotation?.key; break; } case inputKey === "=" && !ctrl && !shift: { if (typeof paragraph.alignJustify !== "boolean") { paragraph.alignJustify = !( paragraphIndex === store.paragraphs.length - 1 ); } paragraph.alignJustify = !paragraph.alignJustify; break; } case inputKey === "c" && ctrl && !shift: { ev.preventDefault(); state.clipboardContent = notation; break; } case inputKey === "c" && ctrl && shift: { ev.preventDefault(); state.clipboardContent = paragraph; break; } case inputKey === "v" && ctrl && !shift: { ev.preventDefault(); if (state.clipboardContent) { switch (state.clipboardContent.type) { case "notation": paragraph.notations.splice( notationIndex + 1, 0, cloneNotation(state.clipboardContent) ); break; case "paragraph": store.paragraphs.splice( paragraphIndex + 1, 0, cloneParagraph(state.clipboardContent) ); break; default: state.clipboardContent = null; } } break; } default: break; } } else { // 仅未选中符号时 switch (true) { case ["h", "j", "k", "l"].includes(inputKey): { const { notation: n } = findParagraphAndNotation( state.lastSelectedNotationKey ); state.selectedNotationKey = ( n || store.paragraphs?.at(0)?.notations?.at(0) )?.key; break; } default: break; } } // 全局 switch (true) { case inputKey === "z" && ctrl && !shift: go(-1); break; case inputKey === "y" && ctrl && !shift: case inputKey === "z" && ctrl && shift: go(1); break; case inputKey === "s" && ctrl && !shift: { ev.preventDefault(); saveFile(); break; } case inputKey === "o" && ctrl && !shift: { ev.preventDefault(); const input = document.createElement("input"); input.type = "file"; input.accept = ".json"; input.onchange = handleOpenFile; input.click(); break; } case inputKey === "?" && !ctrl: { if (state.helpDialogVisible) { state.helpDialogVisible = false; } else { state.configDialogVisible = false; state.helpDialogVisible = true; } break; } default: break; } }); const fileMenu = ( } onClick={handleCreate}> 新建 }> 打开 } onClick={handleSaveFile}> 保存 } onClick={handleExportFile}> 导出为图片 ); const editMenu = ( }> 撤销 }> 重做 }> 添加段落 }> 重置歌曲名称 }> 重置作者信息 ); const convertMenu = ( 1 = C 1 = D 1 = E 1 = F 1 = G 1 = A 1 = B 1 = D 1 = E 1 = G 1 = A 1 = B 升高一个音 降低一个音 升高一个八度 降低一个八度 ); export { fileMenu, editMenu, convertMenu, handleKeyPress, handleClick };