diff --git a/src/menu/editor.js b/src/menu/editor.js index f254a57..8047d0f 100644 --- a/src/menu/editor.js +++ b/src/menu/editor.js @@ -10,13 +10,19 @@ import { import { Menu } from "antd"; import state from "../store/state"; import store from "../store/global"; -import { createNotation, isNote, notations as N } from "../util/notation"; import { + cloneNotation, + createNotation, + isNote, + notations as N, +} from "../util/notation"; +import { + cloneParagraph, createParagraph, createParagraphWithNotations, } from "../util/paragraph"; import { exportFile, loadFile, saveFile } from "../util/file"; -import { findParagraphAndNotation } from "../util/editor"; +import { findParagraphAndNotation, resetGlobalData } from "../util/editor"; import { go, runInWrappedAction, @@ -27,15 +33,18 @@ import { const { SubMenu } = Menu; const handleOpenFile = unwrappedAction((ev) => { - // TODO: working const file = ev.target.files[0]; loadFile(file); }); -const handleSaveFile = unwrappedAction(() => { +const handleSaveFile = () => { saveFile(); -}); -const handleExportFile = () => { +}; +const handleExportFile = unwrappedAction(() => { + state.selectedNotationKey = null; exportFile(); +}); +const handleCreate = () => { + resetGlobalData(); }; const handleEditMenu = wrappedAction(({ key }) => { switch (key) { @@ -149,7 +158,6 @@ const handleKeyPress = wrappedAction((ev) => { } break; } - case inputKey === "l" && !ctrl && !shift: case inputKey === "enter" && !ctrl && !shift: { if (paragraph.notations[notationIndex + 1]) { state.selectedNotationKey = @@ -161,7 +169,15 @@ const handleKeyPress = wrappedAction((ev) => { } break; } - case inputKey === "h" && !ctrl && !shift: + 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 = @@ -169,6 +185,15 @@ const handleKeyPress = wrappedAction((ev) => { } 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 ( @@ -206,6 +231,7 @@ const handleKeyPress = wrappedAction((ev) => { } break; } + case inputKey === "backspace" && !ctrl && !shift: case inputKey === "delete" && !ctrl && !shift: { paragraph.notations.splice(notationIndex, 1); const currNotation = @@ -214,6 +240,17 @@ const handleKeyPress = wrappedAction((ev) => { 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 = !( @@ -224,6 +261,40 @@ const handleKeyPress = wrappedAction((ev) => { 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; } @@ -231,9 +302,12 @@ const handleKeyPress = wrappedAction((ev) => { // 仅未选中符号时 switch (true) { case ["h", "j", "k", "l"].includes(inputKey): { - state.selectedNotationKey = - state.lastSelectedNotationKey || - store.paragraphs?.at(0)?.notations?.at(0).key; + const { notation: n } = findParagraphAndNotation( + state.lastSelectedNotationKey + ); + state.selectedNotationKey = ( + n || store.paragraphs?.at(0)?.notations?.at(0) + )?.key; break; } default: @@ -279,7 +353,7 @@ const handleKeyPress = wrappedAction((ev) => { const fileMenu = (
); diff --git a/src/store/state.js b/src/store/state.js index 003e039..238df78 100644 --- a/src/store/state.js +++ b/src/store/state.js @@ -14,12 +14,8 @@ let globalState = observable({ helpDialogVisible: false, // 配置项弹窗是否可见 configDialogVisible: false, - // 持久化的文件 - file: null, - // ref - refs: { - svg: null, - }, + // 复制剪贴的内容 + clipboardContent: null, }); export default globalState; diff --git a/src/util/editor.js b/src/util/editor.js index 65f8f88..0468860 100644 --- a/src/util/editor.js +++ b/src/util/editor.js @@ -1,4 +1,7 @@ +import { remove, transaction } from "mobx"; +import state from "../store/state"; import store from "../store/global"; +import { clearHistory, unwrappedAction } from "../store/history"; import { createParagraphWithNotations } from "../util/paragraph"; import { VERSION } from "./version"; @@ -49,8 +52,23 @@ function findParagraphAndNotation(key) { return res; } +const resetGlobalData = unwrappedAction((globalData) => { + if (!globalData) { + globalData = getDefaultGlobalDataWidthNotations(); + } + state.lastSelectedNotationKey = state.selectedNotationKey = state.tieSourceKey = null; + clearHistory(); + transaction(() => { + for (const key of Object.keys(store)) { + remove(store, key); + } + Object.assign(store, globalData); + }); +}); + export { getDefaultGlobalData, findParagraphAndNotation, getDefaultGlobalDataWidthNotations, + resetGlobalData, }; diff --git a/src/util/file.js b/src/util/file.js index 551306b..e11603a 100644 --- a/src/util/file.js +++ b/src/util/file.js @@ -1,11 +1,9 @@ import { message } from "antd"; -import { remove, transaction } from "mobx"; import { toJS } from "mobx"; -import globalState from "../store/state"; import store from "../store/global"; -import { clearHistory } from "../store/history"; import domtoimage from "./dom-to-image"; import { VERSION } from "./version"; +import { resetGlobalData } from "./editor"; function read(file) { return new Promise((resolve, reject) => { @@ -23,14 +21,7 @@ async function loadFile(file) { message.error("不支持的文件格式,请使用最新版本"); return; } - globalState.lastSelectedNotationKey = globalState.selectedNotationKey = null; - clearHistory(); - transaction(() => { - for (const key of Object.keys(store)) { - remove(store, key); - } - Object.assign(store, data); - }); + resetGlobalData(data); } function saveFile() { @@ -45,12 +36,14 @@ function saveFile() { } function exportFile() { - domtoimage.toPng(document.querySelector("#temp_svg")).then((dataUrl) => { - const downloadLink = document.createElement("a"); - downloadLink.download = (store.title || "未标题") + ".png"; - downloadLink.href = dataUrl; - downloadLink.click(); - }); + domtoimage + .toPng(document.querySelector("#temp_svg"), { bgcolor: "white" }) + .then((dataUrl) => { + const downloadLink = document.createElement("a"); + downloadLink.download = (store.title || "未标题") + ".png"; + downloadLink.href = dataUrl; + downloadLink.click(); + }); } export { saveFile, loadFile, exportFile }; diff --git a/src/util/notation.js b/src/util/notation.js index ad475c0..e42d3ce 100644 --- a/src/util/notation.js +++ b/src/util/notation.js @@ -1,3 +1,5 @@ +import { toJS } from "mobx"; + const notations = { zero: "0", do: "1", @@ -31,6 +33,7 @@ function isSeparator(notationOrNote) { // 创建符号 function createNotation(initial) { const n = { + type: "notation", key: `n_${String(Math.random())}`, note: "0", octave: 0, @@ -46,5 +49,11 @@ function createNotation(initial) { } return n; } +function cloneNotation(notation) { + const origin = JSON.parse(JSON.stringify(toJS(notation))); + delete origin.key; + const n = createNotation(origin); + return n; +} -export { notations, isNote, isSeparator, createNotation }; +export { notations, isNote, isSeparator, createNotation, cloneNotation }; diff --git a/src/util/paragraph.js b/src/util/paragraph.js index ef6b2d2..f63f8c7 100644 --- a/src/util/paragraph.js +++ b/src/util/paragraph.js @@ -1,8 +1,10 @@ -import { createNotation, notations as N } from "./notation"; +import { toJS } from "mobx"; +import { cloneNotation, createNotation, notations as N } from "./notation"; // 新建段落 function createParagraph(initial) { const p = { + type: "paragraph", key: `p_${String(Math.random())}`, // 符号列表 notations: [], @@ -25,5 +27,11 @@ function createParagraphWithNotations() { ], }); } +function cloneParagraph(paragraph) { + const origin = JSON.parse(JSON.stringify(toJS(paragraph))); + delete origin.key; + origin.notations = paragraph.notations.map((n) => cloneNotation(n)); + return createParagraph(origin); +} -export { createParagraph, createParagraphWithNotations }; +export { createParagraph, createParagraphWithNotations, cloneParagraph }; diff --git a/src/view/Canvas/index.js b/src/view/Canvas/index.js index 8740b78..c9a4e5c 100644 --- a/src/view/Canvas/index.js +++ b/src/view/Canvas/index.js @@ -1,30 +1,19 @@ import { observer } from "mobx-react-lite"; -import { useEffect, useRef } from "react"; -import state from "../../store/state"; import store from "../../store/global"; import Styles from "./index.module.css"; // ENHANCE: 增加整体缩放能力 +// FIXME: 修改获取svg元素的方式 function Canvas({ children, ...props }) { - const ref = useRef(); - useEffect(() => { - state.refs.svg = ref; - return () => { - state.refs.svg = null; - }; - }, []); - return (