feat: 增加复制粘贴功能

This commit is contained in:
2021-10-06 20:03:52 +08:00
parent 2048a89d7e
commit 9d81aaa4fb
9 changed files with 143 additions and 51 deletions
+86 -12
View File
@@ -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 = (
<Menu>
<Menu.Item key="create" icon={<PlusOutlined />}>
<Menu.Item key="create" icon={<PlusOutlined />} onClick={handleCreate}>
新建
</Menu.Item>
<Menu.Item key="open" icon={<FolderOpenOutlined />}>
+1 -1
View File
@@ -58,7 +58,7 @@ const getNotationContextMenu = (notation, paragraph) => {
</Menu.Item>
)}
<Menu.Item key="delete" icon={<DeleteOutlined />}>
删除
删除符号
</Menu.Item>
</Menu>
);
+2 -6
View File
@@ -14,12 +14,8 @@ let globalState = observable({
helpDialogVisible: false,
// 配置项弹窗是否可见
configDialogVisible: false,
// 持久化的文件
file: null,
// ref
refs: {
svg: null,
},
// 复制剪贴的内容
clipboardContent: null,
});
export default globalState;
+18
View File
@@ -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,
};
+5 -12
View File
@@ -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,7 +36,9 @@ function saveFile() {
}
function exportFile() {
domtoimage.toPng(document.querySelector("#temp_svg")).then((dataUrl) => {
domtoimage
.toPng(document.querySelector("#temp_svg"), { bgcolor: "white" })
.then((dataUrl) => {
const downloadLink = document.createElement("a");
downloadLink.download = (store.title || "未标题") + ".png";
downloadLink.href = dataUrl;
+10 -1
View File
@@ -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 };
+10 -2
View File
@@ -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 };
+1 -12
View File
@@ -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 (
<svg
ref={ref}
id="temp_svg"
xmlns=" http://www.w3.org/2000/svg"
className={Styles.svg}
// viewBox={`0 0 ${store.canvasWidth} ${store.canvasHeight}`}
width={store.canvasWidth}
height={store.canvasHeight}
// TODO: do this
onContextMenu={(ev) => ev.preventDefault()}
{...props}
>
+5
View File
@@ -36,6 +36,7 @@ function HelpModal({ visible, onVisibleChange }) {
<KeyboardDescItem kbd="" desc="显示此帮助" />
<KeyboardDescItem kbd="Ctrl+Z" desc="撤销" />
<KeyboardDescItem kbd="Ctrl+Shift+Z" desc="重做" />
<KeyboardDescItem kbd="Ctrl+O" desc="打开文件" />
<KeyboardDescItem kbd="h j k l" desc="移动焦点" />
</ul>
<h3>仅选中符号时</h3>
@@ -55,8 +56,12 @@ function HelpModal({ visible, onVisibleChange }) {
<KeyboardDescItem kbd="Shift+Enter" desc="移动到上一个音符" />
<KeyboardDescItem kbd="Ctrl+Enter" desc="在当前位置后插入音符" />
<KeyboardDescItem kbd="Delete" desc="删除当前音符" />
<KeyboardDescItem kbd="Shift+Delete" desc="删除当前段落" />
<KeyboardDescItem kbd="=" desc="段落是否两端对齐" />
<KeyboardDescItem kbd="h j k l" desc="Vi风格移动焦点" />
<KeyboardDescItem kbd="Ctrl+C" desc="复制当前音符" />
<KeyboardDescItem kbd="Ctrl+Shift+C" desc="复制当前段落" />
<KeyboardDescItem kbd="Ctrl+V" desc="粘贴" />
</ul>
<h2>提示</h2>
<ul className={Styles.list}>