feat: 添加帮助面板
This commit is contained in:
+81
-13
@@ -8,11 +8,10 @@ import {
|
|||||||
UndoOutlined,
|
UndoOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { Menu } from "antd";
|
import { Menu } from "antd";
|
||||||
import { action } from "mobx";
|
|
||||||
import Notation from "../view/Notation";
|
|
||||||
import state from "../store/state";
|
import state from "../store/state";
|
||||||
import store from "../store/global";
|
import store from "../store/global";
|
||||||
import { createNotation, isNote, notations as N } from "../util/notation";
|
import { createNotation, isNote, notations as N } from "../util/notation";
|
||||||
|
import { createParagraph } from "../util/paragraph";
|
||||||
import { findParagraphAndNotation } from "../util/editor";
|
import { findParagraphAndNotation } from "../util/editor";
|
||||||
import { go, runInWrappedAction, wrappedAction } from "../store/history";
|
import { go, runInWrappedAction, wrappedAction } from "../store/history";
|
||||||
|
|
||||||
@@ -38,15 +37,30 @@ const handleEditMenu = wrappedAction(({ key }) => {
|
|||||||
"【记谱者】 记谱",
|
"【记谱者】 记谱",
|
||||||
];
|
];
|
||||||
break;
|
break;
|
||||||
|
case "add-paragraph": {
|
||||||
|
store.paragraphs.push(
|
||||||
|
createParagraph({
|
||||||
|
notations: [
|
||||||
|
createNotation({ note: "0" }),
|
||||||
|
createNotation({ note: "0" }),
|
||||||
|
createNotation({ note: "0" }),
|
||||||
|
createNotation({ note: "0" }),
|
||||||
|
createNotation({ note: N.separator }),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const handleConvertMenu = wrappedAction(() => {
|
const handleConvertMenu = wrappedAction(() => {
|
||||||
|
|
||||||
console.log(111);
|
console.log(111);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 画布上的点击事件
|
||||||
const handleClick = wrappedAction((ev) => {
|
const handleClick = wrappedAction((ev) => {
|
||||||
if (state.shouldNotationBlurAfterClick) {
|
if (state.shouldNotationBlurAfterClick && state.selectedNotationKey) {
|
||||||
|
state.lastSelectedNotationKey = state.selectedNotationKey;
|
||||||
state.selectedNotationKey = null;
|
state.selectedNotationKey = null;
|
||||||
}
|
}
|
||||||
state.shouldNotationBlurAfterClick = true;
|
state.shouldNotationBlurAfterClick = true;
|
||||||
@@ -104,7 +118,7 @@ const handleKeyPress = wrappedAction((ev) => {
|
|||||||
notation.prefixSups.shift();
|
notation.prefixSups.shift();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case inputKey === "b" && !ctrl && !shift:
|
case inputKey === "b" && !ctrl && !shift: {
|
||||||
if (notation.prefixSups.indexOf("♭") !== 0) {
|
if (notation.prefixSups.indexOf("♭") !== 0) {
|
||||||
if (notation.prefixSups[0] === "♯") {
|
if (notation.prefixSups[0] === "♯") {
|
||||||
notation.prefixSups.shift();
|
notation.prefixSups.shift();
|
||||||
@@ -114,14 +128,17 @@ const handleKeyPress = wrappedAction((ev) => {
|
|||||||
notation.prefixSups.shift();
|
notation.prefixSups.shift();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case inputKey === "~" && !ctrl:
|
}
|
||||||
|
case inputKey === "~" && !ctrl: {
|
||||||
if (notation.topDecorators.includes("~")) {
|
if (notation.topDecorators.includes("~")) {
|
||||||
notation.topDecorators.splice(notation.topDecorators.indexOf("~"), 1);
|
notation.topDecorators.splice(notation.topDecorators.indexOf("~"), 1);
|
||||||
} else {
|
} else {
|
||||||
notation.topDecorators.push("~");
|
notation.topDecorators.push("~");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case inputKey === "enter" && !ctrl && !shift:
|
}
|
||||||
|
case inputKey === "l" && !ctrl && !shift:
|
||||||
|
case inputKey === "enter" && !ctrl && !shift: {
|
||||||
if (paragraph.notations[notationIndex + 1]) {
|
if (paragraph.notations[notationIndex + 1]) {
|
||||||
state.selectedNotationKey =
|
state.selectedNotationKey =
|
||||||
paragraph.notations[notationIndex + 1].key;
|
paragraph.notations[notationIndex + 1].key;
|
||||||
@@ -131,40 +148,91 @@ const handleKeyPress = wrappedAction((ev) => {
|
|||||||
state.selectedNotationKey = nextNotation.key;
|
state.selectedNotationKey = nextNotation.key;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case inputKey === "enter" && !ctrl && shift:
|
}
|
||||||
|
case inputKey === "h" && !ctrl && !shift:
|
||||||
|
case inputKey === "enter" && !ctrl && shift: {
|
||||||
if (paragraph.notations[notationIndex - 1]) {
|
if (paragraph.notations[notationIndex - 1]) {
|
||||||
state.selectedNotationKey =
|
state.selectedNotationKey =
|
||||||
paragraph.notations[notationIndex - 1].key;
|
paragraph.notations[notationIndex - 1].key;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case inputKey === "enter" && ctrl && !shift:
|
}
|
||||||
|
case inputKey === "enter" && ctrl && !shift: {
|
||||||
const newNotation = createNotation();
|
const newNotation = createNotation();
|
||||||
paragraph.notations.splice(notationIndex + 1, 0, newNotation);
|
paragraph.notations.splice(notationIndex + 1, 0, newNotation);
|
||||||
state.selectedNotationKey = newNotation.key;
|
state.selectedNotationKey = newNotation.key;
|
||||||
break;
|
break;
|
||||||
case inputKey === "delete":
|
}
|
||||||
|
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 === "delete" && !ctrl && !shift: {
|
||||||
paragraph.notations.splice(notationIndex, 1);
|
paragraph.notations.splice(notationIndex, 1);
|
||||||
const currNotation =
|
const currNotation =
|
||||||
paragraph.notations[notationIndex] ||
|
paragraph.notations[notationIndex] ||
|
||||||
paragraph.notations[notationIndex - 1];
|
paragraph.notations[notationIndex - 1];
|
||||||
state.selectedNotationKey = currNotation?.key;
|
state.selectedNotationKey = currNotation?.key;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
case inputKey === "=" && !ctrl && !shift: {
|
||||||
|
if (typeof paragraph.alignJustify !== "boolean") {
|
||||||
|
paragraph.alignJustify = !(
|
||||||
|
paragraphIndex ===
|
||||||
|
store.paragraphs.length - 1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
paragraph.alignJustify = !paragraph.alignJustify;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 仅未选中符号时
|
||||||
|
switch (true) {
|
||||||
|
case ["h", "j", "k", "l"].includes(inputKey): {
|
||||||
|
state.selectedNotationKey =
|
||||||
|
state.lastSelectedNotationKey ||
|
||||||
|
store.paragraphs?.at(0)?.notations?.at(0).key;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 全局
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case inputKey === "z" && ctrl && !shift:
|
case inputKey === "z" && ctrl && !shift:
|
||||||
console.log("undo");
|
|
||||||
go(-1);
|
go(-1);
|
||||||
break;
|
break;
|
||||||
case inputKey === "y" && ctrl && !shift:
|
case inputKey === "y" && ctrl && !shift:
|
||||||
case inputKey === "z" && ctrl && shift:
|
case inputKey === "z" && ctrl && shift:
|
||||||
console.log("redo");
|
|
||||||
go(1);
|
go(1);
|
||||||
break;
|
break;
|
||||||
|
case inputKey === "?" && !ctrl: {
|
||||||
|
if (state.helpDialogVisible) {
|
||||||
|
state.helpDialogVisible = false;
|
||||||
|
} else {
|
||||||
|
state.configDialogVisible = false;
|
||||||
|
state.helpDialogVisible = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const fileMenu = (
|
const fileMenu = (
|
||||||
<Menu>
|
<Menu>
|
||||||
<Menu.Item key="create" icon={<PlusOutlined />}>
|
<Menu.Item key="create" icon={<PlusOutlined />}>
|
||||||
|
|||||||
+27
-35
@@ -1,8 +1,10 @@
|
|||||||
import {
|
import {
|
||||||
ArrowDownOutlined,
|
ArrowDownOutlined,
|
||||||
ArrowUpOutlined,
|
ArrowUpOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
FontColorsOutlined,
|
FontColorsOutlined,
|
||||||
MinusOutlined,
|
MinusOutlined,
|
||||||
|
PauseOutlined,
|
||||||
PlusOutlined,
|
PlusOutlined,
|
||||||
RadiusSettingOutlined,
|
RadiusSettingOutlined,
|
||||||
StopOutlined,
|
StopOutlined,
|
||||||
@@ -10,27 +12,14 @@ import {
|
|||||||
import { Menu, message } from "antd";
|
import { Menu, message } from "antd";
|
||||||
import state from "../store/state";
|
import state from "../store/state";
|
||||||
import { findParagraphAndNotation } from "../util/editor";
|
import { findParagraphAndNotation } from "../util/editor";
|
||||||
import { isNote } from "../util/notation";
|
import { isNote, isSeparator } from "../util/notation";
|
||||||
import { runInWrappedAction, wrappedAction } from "../store/history";
|
import { runInWrappedAction, wrappedAction } from "../store/history";
|
||||||
|
|
||||||
const getNotationContextMenu = (notation, paragraph) => {
|
const getNotationContextMenu = (notation, paragraph) => {
|
||||||
const hasTie = notation.tieTo;
|
const hasTie = notation.tieTo;
|
||||||
const handleMenu = wrappedAction(function ({ key }) {
|
const handleMenu = wrappedAction(function ({ key }) {
|
||||||
switch (key) {
|
switch (true) {
|
||||||
case "octave-up":
|
case key === "tie":
|
||||||
notation.octave += 1;
|
|
||||||
break;
|
|
||||||
case "octave-down":
|
|
||||||
notation.octave -= 1;
|
|
||||||
break;
|
|
||||||
case "underline-add":
|
|
||||||
notation.underline += 1;
|
|
||||||
break;
|
|
||||||
case "underline-remove":
|
|
||||||
notation.underline -= 1;
|
|
||||||
notation.underline = Math.max(0, notation.underline);
|
|
||||||
break;
|
|
||||||
case "tie":
|
|
||||||
if (!hasTie) {
|
if (!hasTie) {
|
||||||
state.tieSourceKey = notation.key;
|
state.tieSourceKey = notation.key;
|
||||||
return;
|
return;
|
||||||
@@ -41,31 +30,34 @@ const getNotationContextMenu = (notation, paragraph) => {
|
|||||||
}
|
}
|
||||||
notation.tieTo = null;
|
notation.tieTo = null;
|
||||||
break;
|
break;
|
||||||
case "break-underline":
|
case key === "break-underline":
|
||||||
notation.breakUnderline = !notation.breakUnderline;
|
notation.breakUnderline = !notation.breakUnderline;
|
||||||
break;
|
break;
|
||||||
|
case key.startsWith("separator-"): {
|
||||||
|
const separator = key.split("-", 2)[1];
|
||||||
|
notation.note = separator;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu onClick={handleMenu}>
|
// ENHANCE: 分音符类型显示菜单项
|
||||||
<Menu.Item key="octave-up" icon={<ArrowUpOutlined />}>
|
<Menu onClick={handleMenu} style={{ minWidth: "80px" }}>
|
||||||
升高一个八度(8)
|
{
|
||||||
</Menu.Item>
|
<Menu.Item key="break-underline" icon={<StopOutlined />}>
|
||||||
<Menu.Item key="octave-down" icon={<ArrowDownOutlined />}>
|
{notation.breakUnderline
|
||||||
降低一个八度(Shift + 8)
|
? "在此处延续增减时线"
|
||||||
</Menu.Item>
|
: "在此处打断增减时线"}
|
||||||
<Menu.Item key="underline-add" icon={<PlusOutlined />}>
|
</Menu.Item>
|
||||||
添加增减时线(U)
|
}
|
||||||
</Menu.Item>
|
{
|
||||||
<Menu.Item key="underline-remove" icon={<MinusOutlined />}>
|
<Menu.Item key="tie" icon={<RadiusSettingOutlined />}>
|
||||||
减少增减时线(Shift + U)
|
{notation.tieTo ? "删除连音线" : "从此处添加连音线到..."}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item key="break-underline" icon={<StopOutlined />}>
|
}
|
||||||
{notation.breakUnderline ? "在此处延续增减时线" : "在此处打断增减时线"}
|
<Menu.Item key="delete" icon={<DeleteOutlined />}>
|
||||||
</Menu.Item>
|
删除
|
||||||
<Menu.Item key="tie" icon={<RadiusSettingOutlined />}>
|
|
||||||
{notation.tieTo ? "删除连音线" : "从此处添加连音线到..."}
|
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
|
|||||||
+27
-26
@@ -1,44 +1,45 @@
|
|||||||
import {
|
import { DeleteOutlined, EnterOutlined } from "@ant-design/icons";
|
||||||
DeleteOutlined,
|
|
||||||
EnterOutlined,
|
|
||||||
MenuOutlined,
|
|
||||||
PlusCircleOutlined,
|
|
||||||
PlusOutlined,
|
|
||||||
} from "@ant-design/icons";
|
|
||||||
import { Menu } from "antd";
|
import { Menu } from "antd";
|
||||||
import state from "../store/state";
|
|
||||||
import store from "../store/global";
|
import store from "../store/global";
|
||||||
import { go, runInWrappedAction, wrappedAction } from "../store/history";
|
import { createNotation, notations } from "../util/notation";
|
||||||
|
import { createParagraph } from "../util/paragraph";
|
||||||
|
import { wrappedAction } from "../store/history";
|
||||||
|
|
||||||
function getParagraphMenuOptions(paragraph) {
|
function getParagraphMenuOptions(paragraph) {
|
||||||
const handleMenu = wrappedAction(({ key }) => {
|
const handleMenu = wrappedAction(({ key }) => {
|
||||||
console.log(1111111111, key);
|
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case "justify-auto":
|
case "add": {
|
||||||
paragraph.alignJustify = null;
|
const idx = store.paragraphs.findIndex((p) => p.key === paragraph.key);
|
||||||
|
const para = createParagraph({
|
||||||
|
notations: [
|
||||||
|
createNotation({ note: "0" }),
|
||||||
|
createNotation({ note: "0" }),
|
||||||
|
createNotation({ note: "0" }),
|
||||||
|
createNotation({ note: "0" }),
|
||||||
|
createNotation({ note: notations.separator }),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
if (idx !== -1) {
|
||||||
|
store.paragraphs.splice(idx + 1, 0, para);
|
||||||
|
} else {
|
||||||
|
store.paragraphs.push(para);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case "justify-enable":
|
}
|
||||||
paragraph.alignJustify = true;
|
case "delete":
|
||||||
break;
|
const idx = store.paragraphs.findIndex((p) => p.key === paragraph.key);
|
||||||
case "justify-disable":
|
if (idx !== -1) {
|
||||||
paragraph.alignJustify = false;
|
store.paragraphs.splice(idx, 1);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu onClick={handleMenu}>
|
<Menu onClick={handleMenu}>
|
||||||
<Menu.Item key="addNotation" icon={<PlusOutlined />}>
|
<Menu.Item key="add" icon={<EnterOutlined />}>
|
||||||
添加符号
|
|
||||||
</Menu.Item>
|
|
||||||
<Menu.Item key="addParagraph" icon={<EnterOutlined />}>
|
|
||||||
添加段落
|
添加段落
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.SubMenu key="justify" title="两端对齐" icon={<MenuOutlined />}>
|
|
||||||
<Menu.Item key="justify-auto">自动</Menu.Item>
|
|
||||||
<Menu.Item key="justify-enable">启用</Menu.Item>
|
|
||||||
<Menu.Item key="justify-disable">禁用</Menu.Item>
|
|
||||||
</Menu.SubMenu>
|
|
||||||
<Menu.Item danger key="delete" icon={<DeleteOutlined />}>
|
<Menu.Item danger key="delete" icon={<DeleteOutlined />}>
|
||||||
删除段落
|
删除段落
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
|||||||
+2
-145
@@ -1,152 +1,9 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { observable } from "mobx";
|
import { observable } from "mobx";
|
||||||
import { createNotation } from "../util/notation";
|
import { getDefaultGlobalDataWidthNotations } from "../util/editor";
|
||||||
|
|
||||||
// 全局数据及配置,需要持久化
|
// 全局数据及配置,需要持久化
|
||||||
let globalStore = observable({
|
const globalStore = observable(getDefaultGlobalDataWidthNotations());
|
||||||
version: 1,
|
|
||||||
canvasWidth: 1024,
|
|
||||||
canvasHeight: 1448,
|
|
||||||
defaultFontSize: 16,
|
|
||||||
defaultSubFontSize: 12,
|
|
||||||
title: "简谱",
|
|
||||||
tone: "♭D",
|
|
||||||
marginHorizontal: 64,
|
|
||||||
marginTop: 32,
|
|
||||||
gapAfterTitle: 16,
|
|
||||||
gapAfterHeader: 32,
|
|
||||||
gapBetweenParagraph: 32,
|
|
||||||
gapBetweenNotation: 8,
|
|
||||||
beat: [4, 4],
|
|
||||||
speed: 75,
|
|
||||||
authors: ["Haven Mattuy 制谱", "杨瑞光 作词"],
|
|
||||||
paragraphs: [
|
|
||||||
{
|
|
||||||
alignJustify: null,
|
|
||||||
notations: [
|
|
||||||
{
|
|
||||||
note: "6",
|
|
||||||
octave: -2,
|
|
||||||
underline: 2,
|
|
||||||
dotted: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
note: "6",
|
|
||||||
octave: -1,
|
|
||||||
underline: 1,
|
|
||||||
dotted: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
note: "3",
|
|
||||||
octave: 0,
|
|
||||||
underline: 0,
|
|
||||||
dotted: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
note: "3",
|
|
||||||
octave: 0,
|
|
||||||
underline: 0,
|
|
||||||
dotted: false,
|
|
||||||
key: "hk",
|
|
||||||
// tieTo: "ml",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
note: "│",
|
|
||||||
octave: 0,
|
|
||||||
underline: 0,
|
|
||||||
dotted: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "ml",
|
|
||||||
// tieTo: "hk",
|
|
||||||
note: "1",
|
|
||||||
octave: 0,
|
|
||||||
underline: 0,
|
|
||||||
dotted: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
note: "2",
|
|
||||||
// octave: -1,
|
|
||||||
// underline: 2,
|
|
||||||
dotted: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
note: "2",
|
|
||||||
octave: 2,
|
|
||||||
// underline: 1,
|
|
||||||
breakUnderline: true,
|
|
||||||
dotted: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
note: "2",
|
|
||||||
octave: 0,
|
|
||||||
underline: 0,
|
|
||||||
dotted: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
note: "─",
|
|
||||||
octave: 0,
|
|
||||||
underline: 0,
|
|
||||||
dotted: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
note: "│",
|
|
||||||
octave: 0,
|
|
||||||
underline: 0,
|
|
||||||
dotted: false,
|
|
||||||
},
|
|
||||||
].map((obj) => createNotation(obj)),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
notations: [
|
|
||||||
{
|
|
||||||
note: "0",
|
|
||||||
octave: 0,
|
|
||||||
dotted: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
note: "4",
|
|
||||||
octave: 0,
|
|
||||||
dotted: false,
|
|
||||||
// prefixSups: ["♯"],
|
|
||||||
// topDecorators: ["※", "※", "※"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
note: "2",
|
|
||||||
octave: 0,
|
|
||||||
dotted: false,
|
|
||||||
},
|
|
||||||
].map((obj) => createNotation(obj)),
|
|
||||||
alignJustify: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
notations: [
|
|
||||||
{
|
|
||||||
note: "1",
|
|
||||||
octave: 2,
|
|
||||||
dotted: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
note: "2",
|
|
||||||
octave: 0,
|
|
||||||
dotted: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
note: "4",
|
|
||||||
octave: 1,
|
|
||||||
dotted: true,
|
|
||||||
prefixSups: ["♯"],
|
|
||||||
topDecorators: ["※"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
note: "2",
|
|
||||||
octave: 0,
|
|
||||||
dotted: false,
|
|
||||||
},
|
|
||||||
].map((obj) => createNotation(obj)),
|
|
||||||
},
|
|
||||||
].map((obj) => ((obj.key = Math.random()), obj)),
|
|
||||||
});
|
|
||||||
|
|
||||||
const GlobalContext = React.createContext(globalStore);
|
const GlobalContext = React.createContext(globalStore);
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,16 @@ import { observable } from "mobx";
|
|||||||
let globalState = observable({
|
let globalState = observable({
|
||||||
// 当前选中的符号
|
// 当前选中的符号
|
||||||
selectedNotationKey: null,
|
selectedNotationKey: null,
|
||||||
|
// 记录上次选中的符号,之后可恢复焦点
|
||||||
|
lastSelectedNotationKey: null,
|
||||||
// 当前点击事件是否应该让符号失去焦点。应该在符号自身和菜单等点击事件中将其置为false
|
// 当前点击事件是否应该让符号失去焦点。应该在符号自身和菜单等点击事件中将其置为false
|
||||||
shouldNotationBlurAfterClick: true,
|
shouldNotationBlurAfterClick: true,
|
||||||
// 正在操作的连音线起点
|
// 正在操作的连音线起点
|
||||||
tieSourceKey: null,
|
tieSourceKey: null,
|
||||||
|
// 帮助弹窗是否可见
|
||||||
|
helpDialogVisible: false,
|
||||||
|
// 配置项弹窗是否可见
|
||||||
|
configDialogVisible: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default globalState;
|
export default globalState;
|
||||||
|
|||||||
+25
-4
@@ -1,10 +1,12 @@
|
|||||||
import store from "../store/global";
|
import store from "../store/global";
|
||||||
|
import { createNotation, notations } from "../util/notation";
|
||||||
|
import { createParagraph } from "../util/paragraph";
|
||||||
import { VERSION } from "./version";
|
import { VERSION } from "./version";
|
||||||
|
|
||||||
const initialData = {
|
const initialData = {
|
||||||
version: VERSION,
|
version: VERSION,
|
||||||
canvasWidth: 1024,
|
canvasWidth: 896,
|
||||||
canvasHeight: 1448,
|
canvasHeight: 1024,
|
||||||
defaultFontSize: 16,
|
defaultFontSize: 16,
|
||||||
defaultSubFontSize: 12,
|
defaultSubFontSize: 12,
|
||||||
title: "无标题",
|
title: "无标题",
|
||||||
@@ -21,9 +23,24 @@ const initialData = {
|
|||||||
notations: [],
|
notations: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
function getInitialGlobalData() {
|
function getDefaultGlobalData() {
|
||||||
return JSON.parse(JSON.stringify(initialData));
|
return JSON.parse(JSON.stringify(initialData));
|
||||||
}
|
}
|
||||||
|
function getDefaultGlobalDataWidthNotations() {
|
||||||
|
const d = getDefaultGlobalData();
|
||||||
|
d.paragraphs = [
|
||||||
|
createParagraph({
|
||||||
|
notations: [
|
||||||
|
createNotation({ note: "0" }),
|
||||||
|
createNotation({ note: "0" }),
|
||||||
|
createNotation({ note: "0" }),
|
||||||
|
createNotation({ note: "0" }),
|
||||||
|
createNotation({ note: notations.separator }),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
// 根据key查找符号在全局记录中的位置
|
// 根据key查找符号在全局记录中的位置
|
||||||
function findParagraphAndNotation(key) {
|
function findParagraphAndNotation(key) {
|
||||||
@@ -42,4 +59,8 @@ function findParagraphAndNotation(key) {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { getInitialGlobalData, findParagraphAndNotation };
|
export {
|
||||||
|
getDefaultGlobalData,
|
||||||
|
findParagraphAndNotation,
|
||||||
|
getDefaultGlobalDataWidthNotations,
|
||||||
|
};
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import { Button, Form, Input, InputNumber, Modal } from "antd";
|
|||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import store from "../../store/global";
|
import store from "../../store/global";
|
||||||
import { unwrappedAction, wrappedAction } from "../../store/history";
|
import { getDefaultGlobalData } from "../../util/editor";
|
||||||
|
import { unwrappedAction } from "../../store/history";
|
||||||
|
|
||||||
const ImmediateNumberConfigInput = observer(({ propertyName, ...props }) => {
|
const ImmediateNumberConfigInput = observer(({ propertyName, ...props }) => {
|
||||||
const handleChange = unwrappedAction((ev) => {
|
const handleChange = unwrappedAction((ev) => {
|
||||||
@@ -25,17 +26,36 @@ function ConfigModal({ visible, onVisibleChange }) {
|
|||||||
const handleClose = useCallback(() => {
|
const handleClose = useCallback(() => {
|
||||||
onVisibleChange?.call(null, false);
|
onVisibleChange?.call(null, false);
|
||||||
}, [onVisibleChange]);
|
}, [onVisibleChange]);
|
||||||
|
const resetConfig = useCallback(
|
||||||
|
unwrappedAction(() => {
|
||||||
|
const {
|
||||||
|
canvasWidth,
|
||||||
|
canvasHeight,
|
||||||
|
marginTop,
|
||||||
|
marginHorizontal,
|
||||||
|
} = getDefaultGlobalData();
|
||||||
|
Object.assign(store, {
|
||||||
|
canvasWidth,
|
||||||
|
canvasHeight,
|
||||||
|
marginTop,
|
||||||
|
marginHorizontal,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
visible={visible}
|
visible={visible}
|
||||||
title="配置"
|
title="配置"
|
||||||
onCancel={handleClose}
|
onCancel={handleClose}
|
||||||
footer={
|
footer={[
|
||||||
<Button key="ok" type="primary" onClick={handleClose}>
|
<Button key="reset" onClick={resetConfig}>
|
||||||
|
重置为默认
|
||||||
|
</Button>,
|
||||||
|
<Button key="close" type="primary" onClick={handleClose}>
|
||||||
关闭
|
关闭
|
||||||
</Button>
|
</Button>,
|
||||||
}
|
]}
|
||||||
>
|
>
|
||||||
<Form labelAlign="right" labelCol={{ span: 4 }}>
|
<Form labelAlign="right" labelCol={{ span: 4 }}>
|
||||||
<Form.Item label="画布大小">
|
<Form.Item label="画布大小">
|
||||||
@@ -63,6 +83,7 @@ function ConfigModal({ visible, onVisibleChange }) {
|
|||||||
/>
|
/>
|
||||||
</Input.Group>
|
</Input.Group>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<ImmediateNumberConfigItem label="顶部边距" propertyName="marginTop" />
|
||||||
<ImmediateNumberConfigItem
|
<ImmediateNumberConfigItem
|
||||||
label="左右边距"
|
label="左右边距"
|
||||||
propertyName="marginHorizontal"
|
propertyName="marginHorizontal"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
import state from "../../store/state";
|
||||||
import store from "../../store/global";
|
import store from "../../store/global";
|
||||||
import P, {
|
import P, {
|
||||||
calcParagraphAboveOffset,
|
calcParagraphAboveOffset,
|
||||||
@@ -20,15 +21,16 @@ import {
|
|||||||
handleClick,
|
handleClick,
|
||||||
handleKeyPress,
|
handleKeyPress,
|
||||||
} from "../../menu/editor";
|
} from "../../menu/editor";
|
||||||
|
import { unwrappedAction } from "../../store/history";
|
||||||
import Canvas from "../Canvas";
|
import Canvas from "../Canvas";
|
||||||
import ConfigModal from "../ConfigModal";
|
import ConfigModal from "../ConfigModal";
|
||||||
import Header from "../Header";
|
import Header from "../Header";
|
||||||
|
import HelpModal from "../HelpModal";
|
||||||
import Paragraph from "../Paragraph";
|
import Paragraph from "../Paragraph";
|
||||||
import Row from "../Row";
|
import Row from "../Row";
|
||||||
import Styles from "./index.module.css";
|
import Styles from "./index.module.css";
|
||||||
|
|
||||||
function Editor() {
|
function Editor() {
|
||||||
const [isConfigModalVisible, setIsConfigModalVisible] = useState(false);
|
|
||||||
const [isHelpModalVisible, setIsHelpModalVisible] = useState(false);
|
const [isHelpModalVisible, setIsHelpModalVisible] = useState(false);
|
||||||
const heightCache = [];
|
const heightCache = [];
|
||||||
function accumulate(index) {
|
function accumulate(index) {
|
||||||
@@ -43,19 +45,27 @@ function Editor() {
|
|||||||
height += calcParagraphAboveOffset(paragraphs[index]);
|
height += calcParagraphAboveOffset(paragraphs[index]);
|
||||||
return height;
|
return height;
|
||||||
}
|
}
|
||||||
|
const handleChangeConfigModalVisibility = useCallback(
|
||||||
|
unwrappedAction((visible) => {
|
||||||
|
state.configDialogVisible = visible;
|
||||||
|
}, [])
|
||||||
|
);
|
||||||
|
const handleChangeHelpModalVisibility = useCallback(
|
||||||
|
unwrappedAction((visible) => {
|
||||||
|
state.helpDialogVisible = visible;
|
||||||
|
}, [])
|
||||||
|
);
|
||||||
const handleShowConfigDialog = useCallback(() => {
|
const handleShowConfigDialog = useCallback(() => {
|
||||||
setIsConfigModalVisible(true);
|
handleChangeConfigModalVisibility(true);
|
||||||
}, []);
|
}, []);
|
||||||
const handleShowHelpDialog = useCallback(() => {
|
const handleShowHelpDialog = useCallback(() => {
|
||||||
setIsHelpModalVisible(true);
|
handleChangeHelpModalVisibility(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.addEventListener("keydown", handleKeyPress);
|
document.addEventListener("keydown", handleKeyPress);
|
||||||
document.addEventListener("click", handleClick);
|
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener("keydown", handleKeyPress);
|
document.removeEventListener("keydown", handleKeyPress);
|
||||||
document.removeEventListener("click", handleClick);
|
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -100,7 +110,7 @@ function Editor() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Canvas>
|
<Canvas onClick={handleClick}>
|
||||||
<Header />
|
<Header />
|
||||||
<Row offsetX={store.marginHorizontal} offsetY={P.headerOffsetY}>
|
<Row offsetX={store.marginHorizontal} offsetY={P.headerOffsetY}>
|
||||||
{store.paragraphs.map((p, i) => {
|
{store.paragraphs.map((p, i) => {
|
||||||
@@ -120,9 +130,13 @@ function Editor() {
|
|||||||
</Row>
|
</Row>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
<ConfigModal
|
<ConfigModal
|
||||||
visible={isConfigModalVisible}
|
visible={state.configDialogVisible}
|
||||||
onVisibleChange={setIsConfigModalVisible}
|
onVisibleChange={handleChangeConfigModalVisibility}
|
||||||
></ConfigModal>
|
></ConfigModal>
|
||||||
|
<HelpModal
|
||||||
|
visible={state.helpDialogVisible}
|
||||||
|
onVisibleChange={handleChangeHelpModalVisibility}
|
||||||
|
></HelpModal>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,72 @@
|
|||||||
function HelpModal({ visible, onVisibleChange }) {
|
import { Button, Modal } from "antd";
|
||||||
return null;
|
import { useCallback } from "react";
|
||||||
|
import state from "../../store/state";
|
||||||
|
import { unwrappedAction } from "../../store/history";
|
||||||
|
import Styles from "./index.module.css";
|
||||||
|
|
||||||
|
function KeyboardDescItem({ kbd, desc }) {
|
||||||
|
return (
|
||||||
|
<li className={Styles.listItem}>
|
||||||
|
<kbd className={Styles.kbd}>{kbd}</kbd>
|
||||||
|
<span>{desc}</span>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function HelpModal({ visible, onVisibleChange }) {
|
||||||
|
const handleClose = useCallback(() => {
|
||||||
|
onVisibleChange?.call(null, false);
|
||||||
|
}, [onVisibleChange]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
width="100%"
|
||||||
|
height="90%"
|
||||||
|
style={{ top: "4%" }}
|
||||||
|
visible={visible}
|
||||||
|
title="帮助"
|
||||||
|
onCancel={handleClose}
|
||||||
|
footer={
|
||||||
|
<Button key="close" type="primary" onClick={handleClose}>
|
||||||
|
确定
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<h2>快捷键</h2>
|
||||||
|
<h3>全局</h3>
|
||||||
|
<ul className={Styles.list}>
|
||||||
|
<KeyboardDescItem kbd="?" desc="显示此帮助" />
|
||||||
|
<KeyboardDescItem kbd="Ctrl+Z" desc="撤销" />
|
||||||
|
<KeyboardDescItem kbd="Ctrl+Shift+Z" desc="重做" />
|
||||||
|
<KeyboardDescItem kbd="h j k l" desc="移动焦点" />
|
||||||
|
</ul>
|
||||||
|
<h3>仅选中符号时</h3>
|
||||||
|
<h4>输入</h4>
|
||||||
|
<ul className={Styles.list}>
|
||||||
|
<KeyboardDescItem kbd="0~7, -" desc="输入音符" />
|
||||||
|
<KeyboardDescItem kbd="|" desc="输入小节线" />
|
||||||
|
<KeyboardDescItem kbd="." desc="输入附点" />
|
||||||
|
<KeyboardDescItem kbd="~" desc="颤音符号" />
|
||||||
|
<KeyboardDescItem kbd="u / U" desc="增减时线" />
|
||||||
|
<KeyboardDescItem kbd="8 / *" desc="八度圆点" />
|
||||||
|
<KeyboardDescItem kbd="# / b" desc="音符升/降" />
|
||||||
|
</ul>
|
||||||
|
<h4>操作</h4>
|
||||||
|
<ul className={Styles.list}>
|
||||||
|
<KeyboardDescItem kbd="Enter" desc="移动到下一个音符" />
|
||||||
|
<KeyboardDescItem kbd="Shift+Enter" desc="移动到上一个音符" />
|
||||||
|
<KeyboardDescItem kbd="Ctrl+Enter" desc="在当前位置后插入音符" />
|
||||||
|
<KeyboardDescItem kbd="Delete" desc="删除当前音符" />
|
||||||
|
<KeyboardDescItem kbd="=" desc="段落是否两端对齐" />
|
||||||
|
<KeyboardDescItem kbd="h j k l" desc="Vi风格移动焦点" />
|
||||||
|
</ul>
|
||||||
|
<h2>提示</h2>
|
||||||
|
<ul className={Styles.list}>
|
||||||
|
<li>段落上可以右键单击弹出操作菜单</li>
|
||||||
|
<li>增减时线默认连续,可在音符右键菜单中选择打断</li>
|
||||||
|
</ul>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: do this
|
|
||||||
export default HelpModal;
|
export default HelpModal;
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
.list {
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
.listItem {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 8px;
|
||||||
|
margin-right: 16px;
|
||||||
|
min-width: 260px;
|
||||||
|
}
|
||||||
|
.kbd {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: lightgray;
|
||||||
|
padding: 4px;
|
||||||
|
width: 120px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
@@ -120,7 +120,6 @@ function Notation({ offsetX, notation, paragraph }) {
|
|||||||
></circle>
|
></circle>
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EditableContent
|
<EditableContent
|
||||||
inputType="select"
|
inputType="select"
|
||||||
|
|||||||
Reference in New Issue
Block a user