daily: 2021-09-29

This commit is contained in:
2021-09-29 15:41:58 +08:00
parent 47f9643aed
commit 488a2532b2
12 changed files with 313 additions and 92 deletions
+16 -15
View File
@@ -12,7 +12,7 @@ let globalStore = observable({
marginTop: 32, marginTop: 32,
gapAfterTitle: 16, gapAfterTitle: 16,
gapAfterHeader: 32, gapAfterHeader: 32,
gapBetweenParagraph: 32, gapBetweenParagraph: 0,
gapBetweenNotation: 8, gapBetweenNotation: 8,
beat: [4, 4], beat: [4, 4],
speed: 75, speed: 75,
@@ -24,13 +24,13 @@ let globalStore = observable({
{ {
note: "6", note: "6",
octave: -2, octave: -2,
underline: 1, underline: 2,
dotted: true, dotted: true,
}, },
{ {
note: "6", note: "6",
octave: -1, octave: -1,
underline: 0, underline: 1,
dotted: false, dotted: false,
}, },
{ {
@@ -45,7 +45,7 @@ let globalStore = observable({
underline: 0, underline: 0,
dotted: false, dotted: false,
key: "hk", key: "hk",
tieTo: "ml", // tieTo: "ml",
}, },
{ {
note: "│", note: "│",
@@ -55,7 +55,7 @@ let globalStore = observable({
}, },
{ {
key: "ml", key: "ml",
tieTo: "hk", // tieTo: "hk",
note: "1", note: "1",
octave: 0, octave: 0,
underline: 0, underline: 0,
@@ -63,14 +63,14 @@ let globalStore = observable({
}, },
{ {
note: "2", note: "2",
octave: -1, // octave: -1,
underline: 2, // underline: 2,
dotted: false, dotted: false,
}, },
{ {
note: "2", note: "2",
octave: 2, octave: 2,
underline: 1, // underline: 1,
breakUnderline: true, breakUnderline: true,
dotted: false, dotted: false,
}, },
@@ -98,15 +98,15 @@ let globalStore = observable({
notations: [ notations: [
{ {
note: "0", note: "0",
octave: 2, octave: 0,
dotted: false, dotted: false,
}, },
{ {
note: "4", note: "4",
octave: 1, octave: 0,
dotted: false, dotted: false,
prefixSups: ["♯"], // prefixSups: ["♯"],
topDecorators: ["※"], // topDecorators: ["※", "※", "※"],
}, },
{ {
note: "2", note: "2",
@@ -143,9 +143,10 @@ let globalStore = observable({
].map((obj) => ((obj.key = Math.random()), obj)), ].map((obj) => ((obj.key = Math.random()), obj)),
}, },
].map((obj) => ((obj.key = Math.random()), obj)), ].map((obj) => ((obj.key = Math.random()), obj)),
// TODO: do this
popoverRefs: {},
}); });
const GlobalContext = React.createContext(globalStore);
export default globalStore; export default globalStore;
export const GlobalContext = React.createContext(globalStore); export { GlobalContext };
+54
View File
@@ -0,0 +1,54 @@
import { action, intercept, runInAction, toJS } from "mobx";
import globalStore from "./global";
const storeHistory = [];
let cursor = -1;
intercept(globalStore, (change) => {
storeHistory.push(toJS(globalStore));
return change;
});
// 在历史记录中漫游
function go(span) {
const newCursor = Math.min(
Math.max(0, cursor + span),
storeHistory.length - 1
);
if (newCursor === cursor) {
return;
}
cursor = newCursor;
Object.assign(globalStore, storeHistory[newCursor]);
}
// 包装mobx的action以实现历史记录
function executeAction(actionFunc, ...args) {
storeHistory[++cursor] = toJS(globalStore);
storeHistory.length = cursor + 1;
if (storeHistory.length > 256) {
storeHistory.splice(0, 256 - storeHistory.length);
}
actionFunc.apply(this, args);
}
function wrappedAction(...args) {
let name, func;
if (typeof args[0] === "function") {
func = args[0];
} else {
name = args[0];
func = args[1];
}
const realArgs = [name, executeAction.bind(this, func)];
if (name === undefined) {
realArgs.shift();
}
return action.apply(this, realArgs);
}
function runInWrappedAction(func) {
runInAction(() => {
executeAction.call(this, func);
});
}
export { go, wrappedAction, runInWrappedAction };
+76 -13
View File
@@ -1,6 +1,8 @@
import { observable } from "mobx"; import { observable } from "mobx";
import store from "../store/global"; import store from "../store/global";
// ENHANCE: 通过mobx的computed机制缓存一些计算值
let canvas, context2D, defaultFontFamily; let canvas, context2D, defaultFontFamily;
let measureSvgEl, measureTextEl; let measureSvgEl, measureTextEl;
@@ -55,18 +57,35 @@ function _calcTextBox(fontSize, text) {
return measureTextEl.getBBox(); return measureTextEl.getBBox();
} }
// 判断段落中的音符是否有连音线
function _hasTie(paragraph) {
const notations = paragraph?.notations || [];
return notations.some(
(n, i) => n.tieTo && notations.findIndex((nn) => nn.key === n.tieTo) > i
);
}
const placement = observable({ const placement = observable({
get maxContentWidth() { get maxContentWidth() {
return Math.max(store.canvasWidth - store.marginHorizontal * 2, 0); return Math.max(store.canvasWidth - store.marginHorizontal * 2, 0);
}, },
get underlineOffsetY() { get minTieHeight() {
return 8;
},
get maxTieHeight() {
return 25;
},
get underlineStepOffsetY() {
return 3; return 3;
}, },
get underlineInitialOffsetY() {
return this.xHeight / 2 - 3;
},
get octaveStepOffsetY() { get octaveStepOffsetY() {
return 5; return 5;
}, },
get octaveInitialOffsetAbove() { get octaveInitialOffsetAbove() {
return -(placement.xHeight / 2 + 2); return placement.xHeight / 2 + 2;
}, },
get octaveInitialOffsetBelow() { get octaveInitialOffsetBelow() {
return placement.xHeight / 2 - 2; return placement.xHeight / 2 - 2;
@@ -119,13 +138,22 @@ const calcSubTextWidth = _calcTextWidth.bind(null, store.defaultSubFontSize);
// 计算段落行高 // 计算段落行高
function calcParagraphHeight(paragraph) { function calcParagraphHeight(paragraph) {
// TODO: 连音符高度 const notations = paragraph.notations || [];
const maxNoteHeight = Math.max( let tieHeight = 0;
...paragraph.notations.map((n) => calcNotationHeight(n)) if (_hasTie(paragraph)) {
); tieHeight = placement.maxTieHeight;
return maxNoteHeight + store.gapBetweenParagraph; }
// 段落高度不是最高的音符的高度,而是上边偏移最大的音符的上部分偏移量+下边偏移最
// 大的音符的下部分偏移量
const noteHeightMap = notations.map((n) => calcNotationHeight(n));
const aboveOffsetMap = notations.map((n) => calcNotationAboveOffset(n));
const belowOffsetMap = noteHeightMap.map((v, i) => v - aboveOffsetMap[i]);
const maxNoteOffset =
Math.max(...belowOffsetMap) + Math.max(...aboveOffsetMap);
return tieHeight + maxNoteOffset + store.gapBetweenParagraph;
} }
// 计算一行中所有音符的宽度和
function calcParagraphWidth(paragraph) { function calcParagraphWidth(paragraph) {
const notations = paragraph.notations || []; const notations = paragraph.notations || [];
if (notations.length === 0) { if (notations.length === 0) {
@@ -138,6 +166,19 @@ function calcParagraphWidth(paragraph) {
); );
} }
// 计算段落中的音符中心以上偏移量的最大值
function calcParagraphAboveOffset(paragraph) {
const notations = paragraph.notations || [];
let tieHeight = 0;
if (_hasTie(paragraph)) {
tieHeight = placement.maxTieHeight;
}
const notationOffset = notations
.map((n) => calcNotationAboveOffset(n))
.reduce((prev, curr) => Math.max(prev, curr), placement.xHeight / 2);
return notationOffset + tieHeight;
}
// 计算音符中心位置左边的宽度 // 计算音符中心位置左边的宽度
function calcNotationPrefixOffset(notation) { function calcNotationPrefixOffset(notation) {
const prefixes = notation.prefixSups || []; const prefixes = notation.prefixSups || [];
@@ -151,10 +192,9 @@ function calcNotationAboveOffset(notation) {
offsets.push(noteOffsetTop); offsets.push(noteOffsetTop);
let octaveOffset = 0; let octaveOffset = 0;
if (notation.octave > 0) { if (notation.octave > 0) {
octaveOffset = Math.abs( octaveOffset =
placement.octaveInitialOffsetAbove - placement.octaveInitialOffsetAbove +
placement.octaveStepOffsetY * notation.octave placement.octaveStepOffsetY * Math.abs(notation.octave);
);
} }
offsets.push(octaveOffset); offsets.push(octaveOffset);
const topDecoratorOffset = const topDecoratorOffset =
@@ -184,10 +224,20 @@ function calcNotationHeight(notation) {
supOffsetY; supOffsetY;
offsets.push(noteOffsetTop, noteOffsetBottom); offsets.push(noteOffsetTop, noteOffsetBottom);
const oc = notation.octave | 0; const oc = notation.octave | 0;
const underlineOffsetY =
notation.underline > 0
? placement.underlineInitialOffsetY +
placement.underlineStepOffsetY * (notation.underline | 0)
: 0;
offsets.push(underlineOffsetY);
const octaveInitialOffsetY =
notation.octave > 0
? -placement.octaveInitialOffsetAbove
: placement.octaveInitialOffsetBelow + underlineOffsetY;
if (oc > 0) { if (oc > 0) {
octaveOffsetY = placement.octaveInitialOffsetAbove + 5 * oc; octaveOffsetY = octaveInitialOffsetY - 5 * Math.abs(oc);
} else if (oc < 0) { } else if (oc < 0) {
octaveOffsetY = placement.octaveInitialOffsetBelow + 5 * oc; octaveOffsetY = octaveInitialOffsetY + 5 * Math.abs(oc);
} else { } else {
octaveOffsetY = 0; octaveOffsetY = 0;
} }
@@ -196,6 +246,18 @@ function calcNotationHeight(notation) {
// 加上基准以上的半个字符的高度,得到前置上标的最大纵向偏移 // 加上基准以上的半个字符的高度,得到前置上标的最大纵向偏移
supOffsetY = notation.prefixSups?.length ? placement.subXHeight - 4 : 0; supOffsetY = notation.prefixSups?.length ? placement.subXHeight - 4 : 0;
offsets.push(supOffsetY); offsets.push(supOffsetY);
// 顶部装饰符在高八度点之上
let octaveOffsetYAbove = 0;
if (notation.octave > 0) {
octaveOffsetYAbove =
placement.octaveInitialOffsetAbove -
placement.octaveStepOffsetY * notation.octave;
}
const topDecoratorOffset =
octaveOffsetYAbove -
placement.subXHeight * (notation.topDecorators?.length | 0) -
2;
offsets.push(topDecoratorOffset);
const maxOffsetY = Math.max(...offsets); const maxOffsetY = Math.max(...offsets);
const minOffsetY = Math.min(...offsets); const minOffsetY = Math.min(...offsets);
return maxOffsetY - minOffsetY; return maxOffsetY - minOffsetY;
@@ -208,6 +270,7 @@ export {
calcNotationAboveOffset, calcNotationAboveOffset,
calcParagraphWidth, calcParagraphWidth,
calcParagraphHeight, calcParagraphHeight,
calcParagraphAboveOffset,
calcTextWidth, calcTextWidth,
calcSubTextWidth, calcSubTextWidth,
}; };
+2
View File
@@ -2,6 +2,8 @@ import { observer } from "mobx-react-lite";
import store from "../../store/global"; import store from "../../store/global";
import Styles from "./index.module.css"; import Styles from "./index.module.css";
// ENHANCE: 增加整体缩放能力
function Canvas({ children, ...props }) { function Canvas({ children, ...props }) {
return ( return (
<svg <svg
+6
View File
@@ -0,0 +1,6 @@
function ConfigModal({ visible, onVisibleChange }) {
return null;
}
// TODO: do this
export default ConfigModal;
+27 -32
View File
@@ -1,58 +1,41 @@
import SubMenu from "antd/lib/menu/SubMenu"; import { Button, Dropdown } from "antd";
import { Button, Dropdown, Menu } from "antd";
import { import {
EditOutlined, EditOutlined,
FileTextOutlined, FileTextOutlined,
FolderOpenOutlined, SettingOutlined,
PlusOutlined, SyncOutlined,
SaveOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useState } from "react";
import store from "../../store/global"; import store from "../../store/global";
import P, { calcParagraphHeight } from "../../util/placement"; import P, {
calcParagraphAboveOffset,
calcParagraphHeight,
} from "../../util/placement";
import Canvas from "../Canvas"; import Canvas from "../Canvas";
import ConfigModal from "../ConfigModal";
import Header from "../Header"; import Header from "../Header";
import Paragraph from "../Paragraph"; import Paragraph from "../Paragraph";
import Row from "../Row"; import Row from "../Row";
import { convertMenu, editMenu, fileMenu } from "../menu/editor";
import Styles from "./index.module.css"; import Styles from "./index.module.css";
function Editor() { function Editor() {
const [isModalVisible, setIsModalVisible] = useState(false);
const heightCache = []; const heightCache = [];
function accumulate(index) { function accumulate(index) {
const paragraphs = store.paragraphs || [];
let height = 0; let height = 0;
for (let i = 0; i < index; i++) { for (let i = 0; i < index; i++) {
const p = store.paragraphs[i]; const p = paragraphs[i];
heightCache[i] = heightCache[i] || calcParagraphHeight(p); heightCache[i] = heightCache[i] || calcParagraphHeight(p);
height += heightCache[i]; height += heightCache[i];
} }
// 段落渲染定位基于段落中心,因此加上上半部分的偏移量
height += calcParagraphAboveOffset(paragraphs[index]);
return height; return height;
} }
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>
<Menu.Item key="title">编辑曲名</Menu.Item>
<SubMenu key="tone" title="转调">
<Menu.Item key="setting:1">Option 1</Menu.Item>
<Menu.Item key="setting:2">Option 2</Menu.Item>
<Menu.Item key="setting:3">Option 3</Menu.Item>
<Menu.Item key="setting:4">Option 4</Menu.Item>
</SubMenu>
</Menu>
);
return ( return (
<div <div
className={Styles.container} className={Styles.container}
@@ -73,6 +56,14 @@ function Editor() {
编辑 编辑
</Button> </Button>
</Dropdown> </Dropdown>
<Dropdown overlay={convertMenu} placement="bottomLeft">
<Button icon={<SyncOutlined />} type="text">
转调
</Button>
</Dropdown>
<Button icon={<SettingOutlined />} type="text">
配置
</Button>
</div> </div>
</div> </div>
<Canvas> <Canvas>
@@ -94,6 +85,10 @@ function Editor() {
})} })}
</Row> </Row>
</Canvas> </Canvas>
<ConfigModal
visible={isModalVisible}
onVisibleChange={setIsModalVisible}
></ConfigModal>
</div> </div>
); );
} }
+8 -7
View File
@@ -4,32 +4,33 @@ import { observer } from "mobx-react-lite";
import EditableContent from "../../../component/EditableContent"; import EditableContent from "../../../component/EditableContent";
import P from "../../../util/placement"; import P from "../../../util/placement";
import store from "../../../store/global"; import store from "../../../store/global";
import { wrappedAction } from "../../../store/history";
import Row from "../../Row"; import Row from "../../Row";
import Text from "../../Text"; import Text from "../../Text";
const tones = [ const tones = [
"A",
"B",
"C", "C",
"D", "D",
"E", "E",
"F", "F",
"G", "G",
"A", "A",
"B", "B",
"♭C", "♭C",
"♭D", "♭D",
"♭E", "♭E",
"♭F", "♭F",
"♭G", "♭G",
"♭A",
"♭B",
].map((t) => ({ key: t, text: t })); ].map((t) => ({ key: t, text: t }));
const handleChangeTone = action(function (value) { const handleChangeTone = wrappedAction(function (value) {
store.tone = value; store.tone = value;
}); });
const handleChangeSpeed = action(function (value) { const handleChangeSpeed = wrappedAction(function (value) {
store.speed = value; store.speed = value;
}); });
const handleChangeBeat = action(function (value) { const handleChangeBeat = wrappedAction(function (value) {
const beat = String(value).split("/"); const beat = String(value).split("/");
if (beat.length !== 2) { if (beat.length !== 2) {
message.error("请以【*/*】的格式输入节拍!"); message.error("请以【*/*】的格式输入节拍!");
+4 -4
View File
@@ -5,6 +5,7 @@ import { observer } from "mobx-react-lite";
import EditableContent from "../../../component/EditableContent"; import EditableContent from "../../../component/EditableContent";
import P from "../../../util/placement"; import P from "../../../util/placement";
import store from "../../../store/global"; import store from "../../../store/global";
import { wrappedAction } from "../../../store/history";
import Row from "../../Row"; import Row from "../../Row";
import Text from "../../Text"; import Text from "../../Text";
@@ -32,20 +33,19 @@ const blockContextMenu = [
text: "添加作者信息", text: "添加作者信息",
icon: <PlusOutlined style={{ color: "grey" }} />, icon: <PlusOutlined style={{ color: "grey" }} />,
onClick: () => { onClick: () => {
store.authors.push("【作曲者】 作曲");
store.authors.push("【记谱者】 记谱"); store.authors.push("【记谱者】 记谱");
}, },
}, },
]; ];
const handleSelectMenu = action((index, value) => { const handleSelectMenu = wrappedAction((index, value) => {
const menu = authorContextMenu.find((m) => m.key === value); const menu = authorContextMenu.find((m) => m.key === value);
menu?.onClick(index); menu?.onClick(index);
}); });
const handleSelectBlockMenu = action((value) => { const handleSelectBlockMenu = wrappedAction((value) => {
const menu = blockContextMenu.find((m) => m.key === value); const menu = blockContextMenu.find((m) => m.key === value);
menu?.onClick(); menu?.onClick();
}); });
const handleChangeAuthor = action((index, value) => { const handleChangeAuthor = wrappedAction((index, value) => {
if (!value) { if (!value) {
return new Promise((resolve) => { return new Promise((resolve) => {
Modal.confirm({ Modal.confirm({
+2 -7
View File
@@ -3,20 +3,15 @@ import { observer } from "mobx-react-lite";
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import EditableContent from "../../../component/EditableContent"; import EditableContent from "../../../component/EditableContent";
import store from "../../../store/global"; import store from "../../../store/global";
import { wrappedAction } from "../../../store/history";
import Row from "../../Row"; import Row from "../../Row";
import Text from "../../Text"; import Text from "../../Text";
const handleChangeTitle = action((value) => { const handleChangeTitle = wrappedAction((value) => {
store.title = value; store.title = value;
}); });
function Title() { function Title() {
const ref = useRef(); const ref = useRef();
useEffect(() => {
store.popoverRefs.title = ref;
return () => {
store.popoverRefs.title = null;
};
}, []);
return ( return (
<Row type="title" offsetY={store.marginTop}> <Row type="title" offsetY={store.marginTop}>
<EditableContent <EditableContent
+3 -3
View File
@@ -17,10 +17,10 @@ function composeArray(octave) {
} }
function Notation({ offsetX, notation }) { function Notation({ offsetX, notation }) {
const underlineOffset = P.underlineOffsetY * (notation.underline | 0); const underlineOffset = P.underlineStepOffsetY * (notation.underline | 0);
const octaveInitialOffset = const octaveInitialOffset =
notation.octave > 0 notation.octave > 0
? P.octaveInitialOffsetAbove ? -P.octaveInitialOffsetAbove
: P.octaveInitialOffsetBelow + underlineOffset; : P.octaveInitialOffsetBelow + underlineOffset;
const octaveStepOffset = (notation.octave > 0 ? -1 : 1) * P.octaveStepOffsetY; const octaveStepOffset = (notation.octave > 0 ? -1 : 1) * P.octaveStepOffsetY;
let topDecoratorOffset = 0; let topDecoratorOffset = 0;
@@ -82,7 +82,7 @@ function Notation({ offsetX, notation }) {
key={i} key={i}
type="octave" type="octave"
cx="0" cx="0"
cy={octaveStepOffset * i + octaveInitialOffset} cy={octaveInitialOffset + octaveStepOffset * i}
r="2" r="2"
></circle> ></circle>
)); ));
+18 -11
View File
@@ -3,6 +3,7 @@ import P, {
calcNotationAboveOffset, calcNotationAboveOffset,
calcNotationPrefixOffset, calcNotationPrefixOffset,
calcNotationWidth, calcNotationWidth,
calcParagraphAboveOffset,
calcParagraphHeight, calcParagraphHeight,
calcParagraphWidth, calcParagraphWidth,
} from "../../util/placement"; } from "../../util/placement";
@@ -13,9 +14,6 @@ function Paragraph({ paragraph, offsetY, alignJustify }) {
console.log("render paragraph"); console.log("render paragraph");
const notations = paragraph.notations || []; const notations = paragraph.notations || [];
const widthCache = []; const widthCache = [];
const paraOffsetY = Math.max(
...paragraph.notations.map((n) => calcNotationAboveOffset(n))
);
let itemFlexOffset = 0; let itemFlexOffset = 0;
if (alignJustify && paragraph.notations?.length > 1) { if (alignJustify && paragraph.notations?.length > 1) {
const realWidth = calcParagraphWidth(paragraph); const realWidth = calcParagraphWidth(paragraph);
@@ -31,7 +29,9 @@ function Paragraph({ paragraph, offsetY, alignJustify }) {
widthCache[i] = widthCache[i] || calcNotationWidth(n); widthCache[i] = widthCache[i] || calcNotationWidth(n);
width += widthCache[i]; width += widthCache[i];
} }
return width + calcNotationPrefixOffset(notations[index]); // 音符的定位基准是其中心,因此要加上当前音符的前缀部分偏移
width += calcNotationPrefixOffset(notations[index]);
return width;
}; };
const noteOffsets = notations.map((_, i) => { const noteOffsets = notations.map((_, i) => {
@@ -62,7 +62,7 @@ function Paragraph({ paragraph, offsetY, alignJustify }) {
const lines = []; const lines = [];
// 记录音符当前需要绘制的增减时线的条数 // 记录音符当前需要绘制的增减时线的条数
let helpMap = notations.map((n) => (n.underline > 0 ? n.underline : 0)); let helpMap = notations.map((n) => (n.underline > 0 ? n.underline : 0));
let baseOffsetY = P.xHeight / 2 - P.underlineOffsetY; let baseOffsetY = P.underlineInitialOffsetY;
while (helpMap.some((n) => n > 0)) { while (helpMap.some((n) => n > 0)) {
let fromIndex = -1; let fromIndex = -1;
let toIndex = fromIndex; let toIndex = fromIndex;
@@ -98,19 +98,26 @@ function Paragraph({ paragraph, offsetY, alignJustify }) {
} }
} }
} }
baseOffsetY += P.underlineOffsetY; baseOffsetY += P.underlineStepOffsetY;
} }
return lines; return lines;
}; };
// 渲染连音线 // 渲染连音线
const renderTie = (offsetY, fromIndex, toIndex) => { const renderTie = (offsetY, fromIndex, toIndex) => {
const bezierX1 = 0; //noteOffsets[fromIndex]; const bezierX1 = noteOffsets[fromIndex];
const bezierX2 = bezierX1 + 4; const bezierX2 = bezierX1 + 4;
const bezierX4 = 32// noteOffsets[toIndex]; const bezierX4 = noteOffsets[toIndex];
const bezierX3 = bezierX4 - 4; const bezierX3 = bezierX4 - 4;
const bezierY = offsetY - 12; const x = bezierX4 - bezierX1;
console.log(bezierX4 - bezierX1, bezierY - offsetY); // ENHANCE: 优化贝赛尔曲线
let y =
0.000021917145491103032 * x * x * x -
0.0062948773732605465 * x * x +
0.6378785891964498 * x -
0.6843414475200537;
y = Math.max(Math.min(y, P.maxTieHeight), P.minTieHeight);
const bezierY = offsetY - y;
return ( return (
<path <path
d={`M${bezierX1} ${offsetY} C${bezierX2} ${bezierY} ${bezierX3} ${bezierY} ${bezierX4} ${offsetY}`} d={`M${bezierX1} ${offsetY} C${bezierX2} ${bezierY} ${bezierX3} ${bezierY} ${bezierX4} ${offsetY}`}
@@ -148,7 +155,7 @@ function Paragraph({ paragraph, offsetY, alignJustify }) {
}; };
return ( return (
<Row type="paragraph" offsetY={offsetY + paraOffsetY}> <Row type="paragraph" offsetY={offsetY}>
{renderTies()} {renderTies()}
{paragraph.notations.map((n, i) => ( {paragraph.notations.map((n, i) => (
<Notation key={n.key} notation={n} offsetX={noteOffsets[i]} /> <Notation key={n.key} notation={n} offsetX={noteOffsets[i]} />
+97
View File
@@ -0,0 +1,97 @@
import {
EditOutlined,
FileTextOutlined,
FolderOpenOutlined,
PlusOutlined,
SaveOutlined,
} from "@ant-design/icons";
import { Menu } from "antd";
import store from "../../store/global";
import { go, runInWrappedAction } 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-authors">重置作者信息</Menu.Item>
</Menu>
);
const convertMenu = (
<Menu key="tone" title="转调">
<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 }) {
switch (key) {
case "undo":
go(-1);
break;
case "redo":
go(1);
break;
case "reset-title":
runInWrappedAction(() => {
store.title = "【歌曲名称】";
});
break;
case "reset-authors":
store.authors = [
"【作曲者】 作曲",
"【填词者】 填词",
"【记谱者】 记谱",
];
break;
}
}
export { fileMenu, editMenu, convertMenu };