feat: 添加帮助面板

This commit is contained in:
2021-10-06 12:15:58 +08:00
parent cfb73205b1
commit 65a63081fc
11 changed files with 304 additions and 240 deletions
+81 -13
View File
@@ -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,18 +148,42 @@ 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] ||
@@ -150,21 +191,48 @@ const handleKeyPress = wrappedAction((ev) => {
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 />}>
+23 -31
View File
@@ -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,32 +30,35 @@ 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="octave-down" icon={<ArrowDownOutlined />}>
降低一个八度Shift + 8
</Menu.Item>
<Menu.Item key="underline-add" icon={<PlusOutlined />}>
添加增减时线U
</Menu.Item>
<Menu.Item key="underline-remove" icon={<MinusOutlined />}>
减少增减时线Shift + U
</Menu.Item>
<Menu.Item key="break-underline" icon={<StopOutlined />}> <Menu.Item key="break-underline" icon={<StopOutlined />}>
{notation.breakUnderline ? "在此处延续增减时线" : "在此处打断增减时线"} {notation.breakUnderline
? "在此处延续增减时线"
: "在此处打断增减时线"}
</Menu.Item> </Menu.Item>
}
{
<Menu.Item key="tie" icon={<RadiusSettingOutlined />}> <Menu.Item key="tie" icon={<RadiusSettingOutlined />}>
{notation.tieTo ? "删除连音线" : "从此处添加连音线到..."} {notation.tieTo ? "删除连音线" : "从此处添加连音线到..."}
</Menu.Item> </Menu.Item>
}
<Menu.Item key="delete" icon={<DeleteOutlined />}>
删除
</Menu.Item>
</Menu> </Menu>
); );
+27 -26
View File
@@ -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
View File
@@ -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);
+6
View File
@@ -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
View File
@@ -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,
};
+26 -5
View File
@@ -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"
+22 -8
View File
@@ -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>
); );
} }
+69 -3
View File
@@ -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;
+19
View File
@@ -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;
}
-1
View File
@@ -120,7 +120,6 @@ function Notation({ offsetX, notation, paragraph }) {
></circle> ></circle>
)); ));
}; };
return ( return (
<EditableContent <EditableContent
inputType="select" inputType="select"