feat: 优化输入体验
This commit is contained in:
@@ -104,7 +104,7 @@ const EditableContent = function (
|
||||
))}
|
||||
</Menu>
|
||||
);
|
||||
}, [initialValue, options, handleConfirm]);
|
||||
}, [initialValue, options, handleConfirm, handleHideContextMenu, overlay]);
|
||||
let renderContent, renderPopover;
|
||||
switch (inputType) {
|
||||
case "select":
|
||||
|
||||
+28
-15
@@ -1,4 +1,3 @@
|
||||
import { Button, Menu, Upload } from "antd";
|
||||
import {
|
||||
FolderOpenOutlined,
|
||||
HighlightOutlined,
|
||||
@@ -8,10 +7,14 @@ import {
|
||||
SaveOutlined,
|
||||
UndoOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { Menu } from "antd";
|
||||
import state from "../store/state";
|
||||
import store from "../store/global";
|
||||
import { createNotation, isNote, notations as N } from "../util/notation";
|
||||
import { createParagraph } from "../util/paragraph";
|
||||
import {
|
||||
createParagraph,
|
||||
createParagraphWithNotations,
|
||||
} from "../util/paragraph";
|
||||
import { exportFile, loadFile, saveFile } from "../util/file";
|
||||
import { findParagraphAndNotation } from "../util/editor";
|
||||
import {
|
||||
@@ -55,19 +58,11 @@ const handleEditMenu = wrappedAction(({ key }) => {
|
||||
];
|
||||
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 }),
|
||||
],
|
||||
})
|
||||
);
|
||||
store.paragraphs.push(createParagraphWithNotations());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
const handleConvertMenu = wrappedAction(() => {
|
||||
@@ -176,8 +171,19 @@ const handleKeyPress = wrappedAction((ev) => {
|
||||
}
|
||||
case inputKey === "enter" && ctrl && !shift: {
|
||||
const newNotation = createNotation();
|
||||
paragraph.notations.splice(notationIndex + 1, 0, newNotation);
|
||||
state.selectedNotationKey = newNotation.key;
|
||||
if (
|
||||
notationIndex === paragraph.notations.length - 1 &&
|
||||
paragraphIndex === store.paragraphs.length - 1
|
||||
) {
|
||||
// 在最后一个音符时ctrl+enter插入新段落并创建符号
|
||||
store.paragraphs.push(createParagraph({ notations: [newNotation] }));
|
||||
state.selectedNotationKey = store.paragraphs
|
||||
.at(-1)
|
||||
.notations.at(0).key;
|
||||
} else {
|
||||
paragraph.notations.splice(notationIndex + 1, 0, newNotation);
|
||||
state.selectedNotationKey = newNotation.key;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case inputKey === "j" && !ctrl && !shift: {
|
||||
@@ -218,6 +224,8 @@ const handleKeyPress = wrappedAction((ev) => {
|
||||
paragraph.alignJustify = !paragraph.alignJustify;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// 仅未选中符号时
|
||||
@@ -226,7 +234,10 @@ const handleKeyPress = wrappedAction((ev) => {
|
||||
state.selectedNotationKey =
|
||||
state.lastSelectedNotationKey ||
|
||||
store.paragraphs?.at(0)?.notations?.at(0).key;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 全局
|
||||
@@ -261,6 +272,8 @@ const handleKeyPress = wrappedAction((ev) => {
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+15
-61
@@ -1,25 +1,19 @@
|
||||
import {
|
||||
ArrowDownOutlined,
|
||||
ArrowUpOutlined,
|
||||
DeleteOutlined,
|
||||
FontColorsOutlined,
|
||||
MinusOutlined,
|
||||
PauseOutlined,
|
||||
PlusOutlined,
|
||||
RadiusSettingOutlined,
|
||||
StopOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { Menu, message } from "antd";
|
||||
import { Menu } from "antd";
|
||||
import state from "../store/state";
|
||||
import { findParagraphAndNotation } from "../util/editor";
|
||||
import { isNote, isSeparator } from "../util/notation";
|
||||
import { isNote } from "../util/notation";
|
||||
import { runInWrappedAction, wrappedAction } from "../store/history";
|
||||
|
||||
const getNotationContextMenu = (notation, paragraph) => {
|
||||
const hasTie = notation.tieTo;
|
||||
const handleMenu = wrappedAction(function ({ key }) {
|
||||
switch (true) {
|
||||
case key === "tie":
|
||||
case key === "tie": {
|
||||
if (!hasTie) {
|
||||
state.tieSourceKey = notation.key;
|
||||
return;
|
||||
@@ -30,6 +24,7 @@ const getNotationContextMenu = (notation, paragraph) => {
|
||||
}
|
||||
notation.tieTo = null;
|
||||
break;
|
||||
}
|
||||
case key === "break-underline":
|
||||
notation.breakUnderline = !notation.breakUnderline;
|
||||
break;
|
||||
@@ -38,76 +33,35 @@ const getNotationContextMenu = (notation, paragraph) => {
|
||||
notation.note = separator;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
// ENHANCE: 分音符类型显示菜单项
|
||||
<Menu onClick={handleMenu} style={{ minWidth: "80px" }}>
|
||||
{
|
||||
{isNote(notation) && (
|
||||
<Menu.Item key="break-underline" icon={<StopOutlined />}>
|
||||
{notation.breakUnderline
|
||||
? "在此处延续增减时线"
|
||||
: "在此处打断增减时线"}
|
||||
</Menu.Item>
|
||||
}
|
||||
{
|
||||
<Menu.Item key="tie" icon={<RadiusSettingOutlined />}>
|
||||
)}
|
||||
{isNote(notation) && (
|
||||
<Menu.Item
|
||||
key="tie"
|
||||
icon={<RadiusSettingOutlined />}
|
||||
notation={notation}
|
||||
>
|
||||
{notation.tieTo ? "删除连音线" : "从此处添加连音线到..."}
|
||||
</Menu.Item>
|
||||
}
|
||||
)}
|
||||
<Menu.Item key="delete" icon={<DeleteOutlined />}>
|
||||
删除
|
||||
</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
|
||||
? "在此处延续增减时线"
|
||||
: "在此处打断增减时线",
|
||||
visible: isNote(notation),
|
||||
onClick: wrappedAction(() => {
|
||||
notation.breakUnderline = !notation.breakUnderline;
|
||||
}),
|
||||
},
|
||||
{
|
||||
key: "delete",
|
||||
text: "删除",
|
||||
onClick: wrappedAction(() => {
|
||||
const index = paragraph.notations.indexOf(notation);
|
||||
if (index !== -1) {
|
||||
paragraph.notations.splice(index, 1);
|
||||
} else {
|
||||
message.error("符号不存在");
|
||||
}
|
||||
}),
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
// ENHANCE: 使用EditableContent组件的overlay属性传入菜单
|
||||
|
||||
@@ -32,6 +32,8 @@ function getParagraphMenuOptions(paragraph) {
|
||||
store.paragraphs.splice(idx, 1);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { action, intercept, keys, runInAction, toJS } from "mobx";
|
||||
import { action, runInAction, toJS } from "mobx";
|
||||
import globalStore from "./global";
|
||||
|
||||
const storeHistory = [];
|
||||
@@ -43,12 +43,10 @@ function go(span) {
|
||||
Math.max(0, cursor + span),
|
||||
storeHistory.length - 1
|
||||
);
|
||||
console.log("go ", span);
|
||||
if (newCursor === cursor) {
|
||||
return;
|
||||
}
|
||||
cursor = newCursor;
|
||||
console.log("gone to ", newCursor);
|
||||
Object.assign(globalStore, storeHistory[newCursor]);
|
||||
}
|
||||
|
||||
@@ -64,11 +62,6 @@ function executeAction(actionFunc, ...args) {
|
||||
if (!_isEqual(storeHistory[cursor], current)) {
|
||||
storeHistory[++cursor] = current;
|
||||
storeHistory.length = cursor + 1;
|
||||
console.log(
|
||||
"pushed history, length & cursor: ",
|
||||
storeHistory.length,
|
||||
cursor
|
||||
);
|
||||
if (storeHistory.length > 256) {
|
||||
storeHistory.splice(0, 256 - storeHistory.length);
|
||||
}
|
||||
|
||||
+4
-14
@@ -1,6 +1,5 @@
|
||||
import store from "../store/global";
|
||||
import { createNotation, notations } from "../util/notation";
|
||||
import { createParagraph } from "../util/paragraph";
|
||||
import { createParagraphWithNotations } from "../util/paragraph";
|
||||
import { VERSION } from "./version";
|
||||
|
||||
const initialData = {
|
||||
@@ -16,7 +15,7 @@ const initialData = {
|
||||
gapAfterTitle: 16,
|
||||
gapAfterHeader: 32,
|
||||
gapBetweenParagraph: 32,
|
||||
gapBetweenNotation: 8,
|
||||
gapBetweenNotation: 16,
|
||||
beat: [4, 4],
|
||||
speed: 75,
|
||||
authors: ["佚名 作词", "简谱编辑器 制谱"],
|
||||
@@ -28,17 +27,7 @@ function getDefaultGlobalData() {
|
||||
}
|
||||
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 }),
|
||||
],
|
||||
}),
|
||||
];
|
||||
d.paragraphs = [createParagraphWithNotations()];
|
||||
return d;
|
||||
}
|
||||
|
||||
@@ -55,6 +44,7 @@ function findParagraphAndNotation(key) {
|
||||
res.paragraphIndex = i;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
+2
-7
@@ -1,6 +1,7 @@
|
||||
import { message } from "antd";
|
||||
import { remove, transaction } from "mobx";
|
||||
import { toJS } from "mobx";
|
||||
import globalState from "../store/state";
|
||||
import store from "../store/global";
|
||||
import { clearHistory } from "../store/history";
|
||||
import domtoimage from "./dom-to-image";
|
||||
@@ -20,9 +21,9 @@ async function loadFile(file) {
|
||||
const data = await read(file);
|
||||
if (!(data.version <= VERSION)) {
|
||||
message.error("不支持的文件格式,请使用最新版本");
|
||||
console.log(data);
|
||||
return;
|
||||
}
|
||||
globalState.lastSelectedNotationKey = globalState.selectedNotationKey = null;
|
||||
clearHistory();
|
||||
transaction(() => {
|
||||
for (const key of Object.keys(store)) {
|
||||
@@ -44,12 +45,6 @@ function saveFile() {
|
||||
}
|
||||
|
||||
function exportFile() {
|
||||
// const canvas = document.createElement("canvas");
|
||||
// const ctx = canvas.getContext("2d");
|
||||
// const dpi = window.devicePixelRatio || 1;
|
||||
// canvas.style.width = store.canvasWidth;
|
||||
// canvas.style.height = store.canvasHeight;
|
||||
// ctx.scale(dpi, dpi);
|
||||
domtoimage.toPng(document.querySelector("#temp_svg")).then((dataUrl) => {
|
||||
const downloadLink = document.createElement("a");
|
||||
downloadLink.download = (store.title || "未标题") + ".png";
|
||||
|
||||
+14
-1
@@ -1,3 +1,5 @@
|
||||
import { createNotation, notations as N } from "./notation";
|
||||
|
||||
// 新建段落
|
||||
function createParagraph(initial) {
|
||||
const p = {
|
||||
@@ -12,5 +14,16 @@ function createParagraph(initial) {
|
||||
}
|
||||
return p;
|
||||
}
|
||||
function createParagraphWithNotations() {
|
||||
return createParagraph({
|
||||
notations: [
|
||||
createNotation({ note: "0" }),
|
||||
createNotation({ note: "0" }),
|
||||
createNotation({ note: "0" }),
|
||||
createNotation({ note: "0" }),
|
||||
createNotation({ note: N.separator }),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
export { createParagraph };
|
||||
export { createParagraph, createParagraphWithNotations };
|
||||
|
||||
+4
-18
@@ -6,22 +6,6 @@ import store from "../store/global";
|
||||
let canvas, context2D, defaultFontFamily;
|
||||
let measureSvgEl, measureTextEl;
|
||||
|
||||
function _calcFontData(fontSize) {
|
||||
const span = document.createElement("span");
|
||||
span.style.fontFamily = window.getComputedStyle(document.body).fontFamily;
|
||||
span.style.fontSize = `${fontSize}px`;
|
||||
span.style.visibility = "hidden";
|
||||
span.style.position = "absolute";
|
||||
span.style.display = "inline-block";
|
||||
span.textContent = "x";
|
||||
document.body.appendChild(span);
|
||||
const data = span.getBoundingClientRect();
|
||||
return {
|
||||
xWidth: data.width,
|
||||
xHeight: data.height,
|
||||
};
|
||||
}
|
||||
|
||||
function _initializeCanvas(fontSize) {
|
||||
if (!canvas) {
|
||||
canvas = document.createElement("canvas");
|
||||
@@ -151,7 +135,8 @@ function calcParagraphContentHeight(paragraph) {
|
||||
}
|
||||
let tieHeight = 0;
|
||||
if (_hasTie(paragraph)) {
|
||||
tieHeight = placement.maxTieHeight;
|
||||
// FIXME: 应该计算连音线高度
|
||||
// tieHeight = placement.maxTieHeight;
|
||||
}
|
||||
// 段落高度不是最高的音符的高度,而是上边偏移最大的音符的上部分偏移量+下边偏移最
|
||||
// 大的音符的下部分偏移量
|
||||
@@ -184,7 +169,8 @@ function calcParagraphAboveOffset(paragraph) {
|
||||
}
|
||||
let tieHeight = 0;
|
||||
if (_hasTie(paragraph)) {
|
||||
tieHeight = placement.maxTieHeight;
|
||||
// FIXME: 应该计算连音线高度
|
||||
// tieHeight = placement.maxTieHeight;
|
||||
}
|
||||
const notationOffset = notations
|
||||
.map((n) => calcNotationAboveOffset(n))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Button, Form, Input, InputNumber, Modal } from "antd";
|
||||
import { Button, Form, Input, Modal } from "antd";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useCallback } from "react";
|
||||
import store from "../../store/global";
|
||||
@@ -26,25 +26,28 @@ function ConfigModal({ visible, onVisibleChange }) {
|
||||
const handleClose = useCallback(() => {
|
||||
onVisibleChange?.call(null, false);
|
||||
}, [onVisibleChange]);
|
||||
const resetConfig = useCallback(
|
||||
unwrappedAction(() => {
|
||||
const {
|
||||
canvasWidth,
|
||||
canvasHeight,
|
||||
marginTop,
|
||||
marginHorizontal,
|
||||
} = getDefaultGlobalData();
|
||||
Object.assign(store, {
|
||||
canvasWidth,
|
||||
canvasHeight,
|
||||
marginTop,
|
||||
marginHorizontal,
|
||||
});
|
||||
})
|
||||
);
|
||||
const resetConfig = unwrappedAction(() => {
|
||||
const {
|
||||
canvasWidth,
|
||||
canvasHeight,
|
||||
marginTop,
|
||||
marginHorizontal,
|
||||
gapBetweenParagraph,
|
||||
gapBetweenNotation,
|
||||
} = getDefaultGlobalData();
|
||||
Object.assign(store, {
|
||||
canvasWidth,
|
||||
canvasHeight,
|
||||
marginTop,
|
||||
marginHorizontal,
|
||||
gapBetweenParagraph,
|
||||
gapBetweenNotation,
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<Modal
|
||||
style={{ top: 20 }}
|
||||
visible={visible}
|
||||
title="配置"
|
||||
onCancel={handleClose}
|
||||
@@ -88,6 +91,14 @@ function ConfigModal({ visible, onVisibleChange }) {
|
||||
label="左右边距"
|
||||
propertyName="marginHorizontal"
|
||||
/>
|
||||
<ImmediateNumberConfigItem
|
||||
label="段间距"
|
||||
propertyName="gapBetweenParagraph"
|
||||
/>
|
||||
<ImmediateNumberConfigItem
|
||||
label="音符间距"
|
||||
propertyName="gapBetweenNotation"
|
||||
/>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
+11
-17
@@ -7,7 +7,7 @@ import {
|
||||
SettingOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useEffect } from "react";
|
||||
import state from "../../store/state";
|
||||
import store from "../../store/global";
|
||||
import P, {
|
||||
@@ -31,7 +31,6 @@ import Row from "../Row";
|
||||
import Styles from "./index.module.css";
|
||||
|
||||
function Editor() {
|
||||
const [isHelpModalVisible, setIsHelpModalVisible] = useState(false);
|
||||
const heightCache = [];
|
||||
function accumulate(index) {
|
||||
const paragraphs = store.paragraphs || [];
|
||||
@@ -45,23 +44,18 @@ function Editor() {
|
||||
height += calcParagraphAboveOffset(paragraphs[index]);
|
||||
return height;
|
||||
}
|
||||
const handleChangeConfigModalVisibility = useCallback(
|
||||
unwrappedAction((visible) => {
|
||||
state.configDialogVisible = visible;
|
||||
}, [])
|
||||
);
|
||||
const handleChangeHelpModalVisibility = useCallback(
|
||||
unwrappedAction((visible) => {
|
||||
state.helpDialogVisible = visible;
|
||||
}, [])
|
||||
);
|
||||
const handleShowConfigDialog = useCallback(() => {
|
||||
const handleChangeConfigModalVisibility = unwrappedAction((visible) => {
|
||||
state.configDialogVisible = visible;
|
||||
});
|
||||
const handleChangeHelpModalVisibility = unwrappedAction((visible) => {
|
||||
state.helpDialogVisible = visible;
|
||||
});
|
||||
const handleShowConfigDialog = () => {
|
||||
handleChangeConfigModalVisibility(true);
|
||||
}, []);
|
||||
const handleShowHelpDialog = useCallback(() => {
|
||||
};
|
||||
const handleShowHelpDialog = () => {
|
||||
handleChangeHelpModalVisibility(true);
|
||||
}, []);
|
||||
|
||||
};
|
||||
useEffect(() => {
|
||||
document.addEventListener("keydown", handleKeyPress);
|
||||
return () => {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { action } from "mobx";
|
||||
import { message } from "antd";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import EditableContent from "../../../component/EditableContent";
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { DeleteOutlined, PlusOutlined } from "@ant-design/icons";
|
||||
import { Modal } from "antd";
|
||||
import { action } from "mobx";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import EditableContent from "../../../component/EditableContent";
|
||||
import P from "../../../util/placement";
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { action } from "mobx";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useRef } from "react";
|
||||
import EditableContent from "../../../component/EditableContent";
|
||||
import store from "../../../store/global";
|
||||
import { wrappedAction } from "../../../store/history";
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { Button, Modal } from "antd";
|
||||
import { useCallback } from "react";
|
||||
import state from "../../store/state";
|
||||
import { unwrappedAction } from "../../store/history";
|
||||
import Styles from "./index.module.css";
|
||||
|
||||
function KeyboardDescItem({ kbd, desc }) {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { action } from "mobx";
|
||||
import { message } from "antd";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import EditableContent from "../../component/EditableContent";
|
||||
@@ -7,7 +6,7 @@ import store from "../../store/global";
|
||||
import P, { calcSubTextWidth } from "../../util/placement";
|
||||
import { findParagraphAndNotation } from "../../util/editor";
|
||||
import { getNotationContextMenu } from "../../menu/notation";
|
||||
import { notations } from "../../util/notation";
|
||||
import { isNote, notations } from "../../util/notation";
|
||||
import { wrappedAction } from "../../store/history";
|
||||
import Row from "../Row";
|
||||
import Text from "../Text";
|
||||
@@ -40,11 +39,13 @@ function Notation({ offsetX, notation, paragraph }) {
|
||||
state.tieSourceKey = null;
|
||||
return;
|
||||
}
|
||||
if (sourceParagraph.notations.includes(notation)) {
|
||||
if (!sourceParagraph.notations.includes(notation)) {
|
||||
message.warn("只允许相同段落的音符相连!");
|
||||
} else if (!isNote(notation)) {
|
||||
message.warn("只允许在音符上添加连音线!");
|
||||
} else {
|
||||
sourceNotation.tieTo = notation.key;
|
||||
notation.tieTo = sourceNotation.key;
|
||||
} else {
|
||||
message.warn("只允许相同段落的音符相连!");
|
||||
}
|
||||
state.tieSourceKey = null;
|
||||
}
|
||||
|
||||
@@ -8,15 +8,11 @@ import P, {
|
||||
calcParagraphContentHeight,
|
||||
calcParagraphWidth,
|
||||
} from "../../util/placement";
|
||||
import {
|
||||
getParagraphMenuOptions,
|
||||
handleSelectParagraphMenu,
|
||||
} from "../../menu/paragraph";
|
||||
import { getParagraphMenuOptions } from "../../menu/paragraph";
|
||||
import Notation from "../Notation";
|
||||
import Row from "../Row";
|
||||
|
||||
function Paragraph({ paragraph, offsetY, alignJustify }) {
|
||||
console.log("render paragraph");
|
||||
const notations = paragraph.notations || [];
|
||||
const widthCache = [];
|
||||
let itemFlexOffset = 0;
|
||||
|
||||
Reference in New Issue
Block a user