feat: 添加配置面板

This commit is contained in:
2021-10-05 23:45:52 +08:00
parent 615ba6ffba
commit 44b4ef1a44
13 changed files with 524 additions and 124 deletions
+2
View File
@@ -1,5 +1,7 @@
import Editor from "./view/Editor";
import globalStore, { GlobalContext } from "./store/global";
// ENHANCE: 替换掉antd组件库。。一堆莫名其妙的毛病
function App() {
return (
<GlobalContext.Provider value={globalStore}>
+18 -1
View File
@@ -4,7 +4,18 @@ import PopoverOnSvg from "../PopoverOnSvg";
import Styles from "./index.module.css";
const EditableContent = function (
{ children, title, initialValue, inputType, options, onChange, popoverProps },
{
children,
title,
initialValue,
inputType,
// ENHANCE: 去除这个prop,使用overlay代替
options,
// 选择模式要渲染的菜单,如果没有提供则根据options生成
overlay,
onChange,
popoverProps,
},
ref
) {
const [inputValue, setInputValue] = useState(initialValue);
@@ -37,6 +48,9 @@ const EditableContent = function (
},
[inputValue, onChange]
);
const handleHideContextMenu = useCallback(() => {
setPopoverVisible(false);
}, []);
const handleCancel = useCallback(
function () {
setInputValue(initialValue);
@@ -68,6 +82,9 @@ const EditableContent = function (
[inputValue, handleInput, handleConfirm, handleCancel]
);
const renderSelectionPopover = useCallback(() => {
if (overlay) {
return <div onClick={handleHideContextMenu}>{overlay}</div>;
}
return (
<Menu className={Styles.menu}>
{(options || [])
+83 -63
View File
@@ -1,7 +1,11 @@
import {
FolderOpenOutlined,
HighlightOutlined,
MenuUnfoldOutlined,
PlusOutlined,
RedoOutlined,
SaveOutlined,
UndoOutlined,
} from "@ant-design/icons";
import { Menu } from "antd";
import { action } from "mobx";
@@ -13,69 +17,8 @@ import { findParagraphAndNotation } from "../util/editor";
import { go, runInWrappedAction, wrappedAction } from "../store/history";
const { SubMenu } = Menu;
const fileMenu = (
<Menu>
<Menu.Item key="create" icon={<PlusOutlined />}>
新建
</Menu.Item>
<Menu.Item key="open" icon={<FolderOpenOutlined />}>
打开
</Menu.Item>
<Menu.Item key="save" icon={<SaveOutlined />}>
保存
</Menu.Item>
</Menu>
);
const editMenu = (
<Menu onClick={handleMenu}>
<Menu.Item key="undo">撤销</Menu.Item>
<Menu.Item key="redo">重做</Menu.Item>
<Menu.Item key="reset-title">添加段落</Menu.Item>
<Menu.Item key="reset-title">重置歌曲名称</Menu.Item>
<Menu.Item key="reset-authors">重置作者信息</Menu.Item>
</Menu>
);
const convertMenu = (
<Menu key="tone">
<SubMenu key="convert-to" title="转到...">
<Menu.Item key="convert-to-C">1 = C</Menu.Item>
<Menu.Item key="convert-to-D">1 = D</Menu.Item>
<Menu.Item key="convert-to-E">1 = E</Menu.Item>
<Menu.Item key="convert-to-F">1 = F</Menu.Item>
<Menu.Item key="convert-to-G">1 = G</Menu.Item>
<Menu.Item key="convert-to-A">1 = A</Menu.Item>
<Menu.Item key="convert-to-B">1 = B</Menu.Item>
<Menu.Item key="convert-to-bC">
1 = <sup></sup>C
</Menu.Item>
<Menu.Item key="convert-to-bD">
1 = <sup></sup>D
</Menu.Item>
<Menu.Item key="convert-to-bE">
1 = <sup></sup>E
</Menu.Item>
<Menu.Item key="convert-to-bF">
1 = <sup></sup>F
</Menu.Item>
<Menu.Item key="convert-to-bG">
1 = <sup></sup>G
</Menu.Item>
<Menu.Item key="convert-to-bA">
1 = <sup></sup>A
</Menu.Item>
<Menu.Item key="convert-to-bB">
1 = <sup></sup>B
</Menu.Item>
</SubMenu>
<Menu.Item key="convert-up">升高一个音</Menu.Item>
<Menu.Item key="convert-down">降低一个音</Menu.Item>
<Menu.Item key="convert-up8">升高一个八度</Menu.Item>
<Menu.Item key="convert-down8">降低一个八度</Menu.Item>
</Menu>
);
function handleMenu({ key }) {
const handleEditMenu = wrappedAction(({ key }) => {
switch (key) {
case "undo":
go(-1);
@@ -96,7 +39,11 @@ function handleMenu({ key }) {
];
break;
}
}
});
const handleConvertMenu = wrappedAction(() => {
console.log(111);
});
const handleClick = wrappedAction((ev) => {
if (state.shouldNotationBlurAfterClick) {
@@ -217,4 +164,77 @@ const handleKeyPress = wrappedAction((ev) => {
}
});
const fileMenu = (
<Menu>
<Menu.Item key="create" icon={<PlusOutlined />}>
新建
</Menu.Item>
<Menu.Item key="open" icon={<FolderOpenOutlined />}>
打开
</Menu.Item>
<Menu.Item key="save" icon={<SaveOutlined />}>
保存
</Menu.Item>
</Menu>
);
const editMenu = (
<Menu onClick={handleEditMenu}>
<Menu.Item key="undo" icon={<UndoOutlined />}>
撤销
</Menu.Item>
<Menu.Item key="redo" icon={<RedoOutlined />}>
重做
</Menu.Item>
<Menu.Item key="add-paragraph" icon={<MenuUnfoldOutlined />}>
添加段落
</Menu.Item>
<Menu.Item key="reset-title" icon={<HighlightOutlined />}>
重置歌曲名称
</Menu.Item>
<Menu.Item key="reset-authors" icon={<HighlightOutlined />}>
重置作者信息
</Menu.Item>
</Menu>
);
const convertMenu = (
<Menu key="tone" onClick={handleConvertMenu}>
<SubMenu key="convert-to" title="转到...">
<Menu.Item key="convert-to-C">1 = C</Menu.Item>
<Menu.Item key="convert-to-D">1 = D</Menu.Item>
<Menu.Item key="convert-to-E">1 = E</Menu.Item>
<Menu.Item key="convert-to-F">1 = F</Menu.Item>
<Menu.Item key="convert-to-G">1 = G</Menu.Item>
<Menu.Item key="convert-to-A">1 = A</Menu.Item>
<Menu.Item key="convert-to-B">1 = B</Menu.Item>
<Menu.Item key="convert-to-bC">
1 = <sup></sup>C
</Menu.Item>
<Menu.Item key="convert-to-bD">
1 = <sup></sup>D
</Menu.Item>
<Menu.Item key="convert-to-bE">
1 = <sup></sup>E
</Menu.Item>
<Menu.Item key="convert-to-bF">
1 = <sup></sup>F
</Menu.Item>
<Menu.Item key="convert-to-bG">
1 = <sup></sup>G
</Menu.Item>
<Menu.Item key="convert-to-bA">
1 = <sup></sup>A
</Menu.Item>
<Menu.Item key="convert-to-bB">
1 = <sup></sup>B
</Menu.Item>
</SubMenu>
<Menu.Item key="convert-up">升高一个音</Menu.Item>
<Menu.Item key="convert-down">降低一个音</Menu.Item>
<Menu.Item key="convert-up8">升高一个八度</Menu.Item>
<Menu.Item key="convert-down8">降低一个八度</Menu.Item>
</Menu>
);
export { fileMenu, editMenu, convertMenu, handleKeyPress, handleClick };
+78 -24
View File
@@ -1,33 +1,36 @@
import { message } from "antd";
import {
ArrowDownOutlined,
ArrowUpOutlined,
FontColorsOutlined,
MinusOutlined,
PlusOutlined,
RadiusSettingOutlined,
StopOutlined,
} from "@ant-design/icons";
import { Menu, message } from "antd";
import state from "../store/state";
import { findParagraphAndNotation } from "../util/editor";
import { isNote } from "../util/notation";
import { runInWrappedAction, wrappedAction } from "../store/history";
const getNotationContextItems = (notation, paragraph) => {
const getNotationContextMenu = (notation, paragraph) => {
const hasTie = notation.tieTo;
return [
{
key: "octaveUp",
text: "升高一个八度",
visible: isNote(notation),
onClick: wrappedAction(() => {
const handleMenu = wrappedAction(function ({ key }) {
switch (key) {
case "octave-up":
notation.octave += 1;
}),
},
{
key: "octaveDown",
text: "降低一个八度",
visible: isNote(notation),
onClick: wrappedAction(() => {
break;
case "octave-down":
notation.octave -= 1;
}),
},
{
key: "tie",
text: notation.tieTo ? "删除连音线" : "从此处添加连音线到...",
visible: isNote(notation),
onClick: wrappedAction(() => {
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) {
state.tieSourceKey = notation.key;
return;
@@ -37,8 +40,59 @@ const getNotationContextItems = (notation, paragraph) => {
tieDesc.tieTo = null;
}
notation.tieTo = null;
break;
case "break-underline":
notation.breakUnderline = !notation.breakUnderline;
break;
}
});
return (
<Menu onClick={handleMenu}>
<Menu.Item key="octave-up" icon={<ArrowUpOutlined />}>
升高一个八度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 />}>
{notation.breakUnderline ? "在此处延续增减时线" : "在此处打断增减时线"}
</Menu.Item>
<Menu.Item key="tie" icon={<RadiusSettingOutlined />}>
{notation.tieTo ? "删除连音线" : "从此处添加连音线到..."}
</Menu.Item>
</Menu>
);
return [
{
key: "octaveUp",
text: "升高一个八度(8",
visible: isNote(notation),
onClick: wrappedAction(() => {
notation.octave += 1;
}),
},
{
key: "octaveDown",
text: "降低一个八度(Shift + 8",
visible: isNote(notation),
onClick: wrappedAction(() => {
notation.octave -= 1;
}),
},
{
key: "tie",
text: notation.tieTo ? "删除连音线" : "从此处添加连音线到...",
visible: isNote(notation),
onClick: wrappedAction(() => {}),
},
{
key: "break-underline",
text: notation.breakUnderline
@@ -64,7 +118,7 @@ const getNotationContextItems = (notation, paragraph) => {
];
};
// ENHANCE: 改变<EditableContent>组件这个反人类的回调执行
// ENHANCE: 使用EditableContent组件的overlay属性传入菜单
// 执行选项回调。。
function handleNotationContext(options, key) {
runInWrappedAction(() => (state.shouldNotationBlurAfterClick = false));
@@ -72,4 +126,4 @@ function handleNotationContext(options, key) {
callback && callback();
}
export { getNotationContextItems, handleNotationContext };
export { getNotationContextMenu, handleNotationContext };
+2 -10
View File
@@ -11,8 +11,8 @@ import store from "../store/global";
import { go, runInWrappedAction, wrappedAction } from "../store/history";
function getParagraphMenuOptions(paragraph) {
return [];
const handleMenu = wrappedAction(({ key }) => {
console.log(1111111111, key);
switch (key) {
case "justify-auto":
paragraph.alignJustify = null;
@@ -46,12 +46,4 @@ function getParagraphMenuOptions(paragraph) {
);
}
// ENHANCE: 同notation的菜单项处理函数,改改改
// TODO: working 现在就改!然后EditableContent应该直接传入antd的菜单项
_function handleSelectParagraphMenu(options, key) {
runInWrappedAction(() => (state.shouldNotationBlurAfterClick = false));
const callback = options.find((ops) => ops.key === key)?.onClick;
callback && callback();
}
export { getParagraphMenuOptions, handleSelectParagraphMenu };
export { getParagraphMenuOptions };
+10 -1
View File
@@ -88,5 +88,14 @@ function runInWrappedAction(func) {
executeAction.call(this, func);
});
}
// 不需要记录历史记录的时候直接使用原action
const unwrappedAction = action;
const runInUnwrappedAction = runInAction;
export { go, wrappedAction, runInWrappedAction };
export {
go,
wrappedAction,
runInWrappedAction,
unwrappedAction,
runInUnwrappedAction,
};
+72 -3
View File
@@ -1,6 +1,75 @@
function ConfigModal({ visible, onVisibleChange }) {
return null;
import { Button, Form, Input, InputNumber, Modal } from "antd";
import { observer } from "mobx-react-lite";
import { useCallback } from "react";
import store from "../../store/global";
import { unwrappedAction, wrappedAction } from "../../store/history";
const ImmediateNumberConfigInput = observer(({ propertyName, ...props }) => {
const handleChange = unwrappedAction((ev) => {
const v = Number(ev.target.value) | 0;
store[propertyName] = v;
});
return (
<Input value={store[propertyName]} onChange={handleChange} {...props} />
);
});
function ImmediateNumberConfigItem({ propertyName, label, ...props }) {
return (
<Form.Item label={label} {...props}>
<ImmediateNumberConfigInput propertyName={propertyName} />
</Form.Item>
);
}
function ConfigModal({ visible, onVisibleChange }) {
const handleClose = useCallback(() => {
onVisibleChange?.call(null, false);
}, [onVisibleChange]);
return (
<Modal
visible={visible}
title="配置"
onCancel={handleClose}
footer={
<Button key="ok" type="primary" onClick={handleClose}>
关闭
</Button>
}
>
<Form labelAlign="right" labelCol={{ span: 4 }}>
<Form.Item label="画布大小">
<Input.Group>
<ImmediateNumberConfigInput
propertyName="canvasWidth"
style={{ width: "45%", textAlign: "center" }}
/>
<Input
className="site-input-split"
style={{
width: "10%",
borderLeft: 0,
borderRight: 0,
textAlign: "center",
pointerEvents: "none",
}}
placeholder="x"
disabled
/>
<ImmediateNumberConfigInput
propertyName="canvasHeight"
style={{ width: "45%", textAlign: "center" }}
/>
</Input.Group>
</Form.Item>
<ImmediateNumberConfigItem
label="左右边距"
propertyName="marginHorizontal"
/>
</Form>
</Modal>
);
}
// TODO: do this
export default ConfigModal;
+24 -6
View File
@@ -2,12 +2,12 @@ import { Button, Dropdown } from "antd";
import {
EditOutlined,
FileTextOutlined,
QuestionOutlined,
RetweetOutlined,
SettingOutlined,
SyncOutlined,
} from "@ant-design/icons";
import { observer } from "mobx-react-lite";
import { useEffect, useState } from "react";
import { useCallback, useEffect, useState } from "react";
import store from "../../store/global";
import P, {
calcParagraphAboveOffset,
@@ -28,7 +28,8 @@ import Row from "../Row";
import Styles from "./index.module.css";
function Editor() {
const [isModalVisible, setIsModalVisible] = useState(false);
const [isConfigModalVisible, setIsConfigModalVisible] = useState(false);
const [isHelpModalVisible, setIsHelpModalVisible] = useState(false);
const heightCache = [];
function accumulate(index) {
const paragraphs = store.paragraphs || [];
@@ -42,6 +43,12 @@ function Editor() {
height += calcParagraphAboveOffset(paragraphs[index]);
return height;
}
const handleShowConfigDialog = useCallback(() => {
setIsConfigModalVisible(true);
}, []);
const handleShowHelpDialog = useCallback(() => {
setIsHelpModalVisible(true);
}, []);
useEffect(() => {
document.addEventListener("keydown", handleKeyPress);
@@ -77,9 +84,20 @@ function Editor() {
转调
</Button>
</Dropdown>
<Button icon={<SettingOutlined />} type="text">
<Button
icon={<SettingOutlined />}
type="text"
onClick={handleShowConfigDialog}
>
配置
</Button>
<Button
icon={<QuestionOutlined />}
type="text"
onClick={handleShowHelpDialog}
>
帮助
</Button>
</div>
</div>
<Canvas>
@@ -102,8 +120,8 @@ function Editor() {
</Row>
</Canvas>
<ConfigModal
visible={isModalVisible}
onVisibleChange={setIsModalVisible}
visible={isConfigModalVisible}
onVisibleChange={setIsConfigModalVisible}
></ConfigModal>
</div>
);
+3 -3
View File
@@ -13,7 +13,7 @@ const authorContextMenu = [
{
key: "create",
text: "添加",
icon: <PlusOutlined style={{ color: "grey" }} />,
icon: <PlusOutlined />,
onClick: () => {
store.authors.push("作者信息");
},
@@ -21,7 +21,7 @@ const authorContextMenu = [
{
key: "delete",
text: "删除",
icon: <DeleteOutlined style={{ color: "grey" }} />,
icon: <DeleteOutlined />,
onClick: (index) => {
store.authors.splice(index, 1);
},
@@ -31,7 +31,7 @@ const blockContextMenu = [
{
key: "create",
text: "添加作者信息",
icon: <PlusOutlined style={{ color: "grey" }} />,
icon: <PlusOutlined />,
onClick: () => {
store.authors.push("【记谱者】 记谱");
},
+6
View File
@@ -0,0 +1,6 @@
function HelpModal({ visible, onVisibleChange }) {
return null;
}
// TODO: do this
export default HelpModal;
+3 -11
View File
@@ -4,15 +4,9 @@ import { observer } from "mobx-react-lite";
import EditableContent from "../../component/EditableContent";
import state from "../../store/state";
import store from "../../store/global";
import P, {
calcNotationPrefixOffset,
calcSubTextWidth,
} from "../../util/placement";
import P, { calcSubTextWidth } from "../../util/placement";
import { findParagraphAndNotation } from "../../util/editor";
import {
getNotationContextItems,
handleNotationContext,
} from "../../menu/notation";
import { getNotationContextMenu } from "../../menu/notation";
import { notations } from "../../util/notation";
import { wrappedAction } from "../../store/history";
import Row from "../Row";
@@ -128,13 +122,11 @@ function Notation({ offsetX, notation, paragraph }) {
));
};
const options = getNotationContextItems(notation, paragraph);
return (
<EditableContent
inputType="select"
options={options}
popoverProps={{ trigger: "context" }}
onChange={handleNotationContext.bind(null, options)}
overlay={getNotationContextMenu(notation, paragraph)}
>
<Row
editable
+1 -2
View File
@@ -166,8 +166,7 @@ function Paragraph({ paragraph, offsetY, alignJustify }) {
return (
<EditableContent
inputType="select"
options={getParagraphMenuOptions(paragraph)}
onChange={handleSelectParagraphMenu}
overlay={getParagraphMenuOptions(paragraph)}
>
<rect
x={-P.xWidth / 2}