|
@@ -109,6 +109,7 @@ import useIsMobile, { IsMobileProvider } from "./is-mobile";
|
|
|
import { copyToAppClipboard, getClipboardContent } from "./clipboard";
|
|
|
import { normalizeScroll } from "./scene/data";
|
|
|
import { getCenter, getDistance } from "./gesture";
|
|
|
+import { menu, palette } from "./components/icons";
|
|
|
|
|
|
let { elements } = createScene();
|
|
|
const { history } = createHistory();
|
|
@@ -286,9 +287,11 @@ const LayerUI = React.memo(
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- const showSelectedShapeActions =
|
|
|
- (appState.editingElement || getSelectedElements(elements).length) &&
|
|
|
- appState.elementType === "selection";
|
|
|
+ const showSelectedShapeActions = Boolean(
|
|
|
+ appState.editingElement ||
|
|
|
+ getSelectedElements(elements).length ||
|
|
|
+ appState.elementType !== "selection",
|
|
|
+ );
|
|
|
|
|
|
function renderSelectedShapeActions() {
|
|
|
const { elementType, editingElement } = appState;
|
|
@@ -386,21 +389,6 @@ const LayerUI = React.memo(
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- const lockButton = (
|
|
|
- <LockIcon
|
|
|
- checked={appState.elementLocked}
|
|
|
- onChange={() => {
|
|
|
- setAppState({
|
|
|
- elementLocked: !appState.elementLocked,
|
|
|
- elementType: appState.elementLocked
|
|
|
- ? "selection"
|
|
|
- : appState.elementType,
|
|
|
- });
|
|
|
- }}
|
|
|
- title={t("toolBar.lock")}
|
|
|
- />
|
|
|
- );
|
|
|
-
|
|
|
return isMobile ? (
|
|
|
<>
|
|
|
{appState.openedMenu === "canvas" ? (
|
|
@@ -411,13 +399,24 @@ const LayerUI = React.memo(
|
|
|
<h2 className="visually-hidden" id="canvas-actions-title">
|
|
|
{t("headings.canvasActions")}
|
|
|
</h2>
|
|
|
- <div className="App-mobile-menu-scroller">
|
|
|
+ <div className="App-mobile-menu-scroller panelColumn">
|
|
|
<Stack.Col gap={4}>
|
|
|
{actionManager.renderAction("loadScene")}
|
|
|
{actionManager.renderAction("saveScene")}
|
|
|
{renderExportDialog()}
|
|
|
{actionManager.renderAction("clearCanvas")}
|
|
|
{actionManager.renderAction("changeViewBackgroundColor")}
|
|
|
+ <fieldset>
|
|
|
+ <legend>{t("labels.language")}</legend>
|
|
|
+ <LanguageList
|
|
|
+ onChange={lng => {
|
|
|
+ setLanguage(lng);
|
|
|
+ setAppState({});
|
|
|
+ }}
|
|
|
+ languages={languages}
|
|
|
+ currentLanguage={language}
|
|
|
+ />
|
|
|
+ </fieldset>
|
|
|
</Stack.Col>
|
|
|
</div>
|
|
|
</section>
|
|
@@ -456,61 +455,57 @@ const LayerUI = React.memo(
|
|
|
</FixedSideContainer>
|
|
|
<footer className="App-toolbar">
|
|
|
<div className="App-toolbar-content">
|
|
|
- <ToolButton
|
|
|
- type="button"
|
|
|
- icon={
|
|
|
- <span style={{ fontSize: "2em", marginTop: "-0.15em" }}>☰</span>
|
|
|
- }
|
|
|
- aria-label={t("buttons.menu")}
|
|
|
- onClick={() =>
|
|
|
- setAppState(({ openedMenu }: any) => ({
|
|
|
- openedMenu: openedMenu === "canvas" ? null : "canvas",
|
|
|
- }))
|
|
|
- }
|
|
|
- />
|
|
|
- <div
|
|
|
- style={{
|
|
|
- visibility: isSomeElementSelected(elements)
|
|
|
- ? "visible"
|
|
|
- : "hidden",
|
|
|
- }}
|
|
|
- >
|
|
|
- {" "}
|
|
|
- {actionManager.renderAction("deleteSelectedElements")}
|
|
|
- </div>
|
|
|
- {lockButton}
|
|
|
- {actionManager.renderAction("finalize")}
|
|
|
- <div
|
|
|
- style={{
|
|
|
- visibility: isSomeElementSelected(elements)
|
|
|
- ? "visible"
|
|
|
- : "hidden",
|
|
|
- }}
|
|
|
- >
|
|
|
- <ToolButton
|
|
|
- type="button"
|
|
|
- icon={
|
|
|
- <span style={{ fontSize: "2em", marginTop: "-0.15em" }}>
|
|
|
- ✎
|
|
|
- </span>
|
|
|
- }
|
|
|
- aria-label={t("buttons.menu")}
|
|
|
- onClick={() =>
|
|
|
- setAppState(({ openedMenu }: any) => ({
|
|
|
- openedMenu: openedMenu === "shape" ? null : "shape",
|
|
|
- }))
|
|
|
- }
|
|
|
- />
|
|
|
- </div>
|
|
|
- {appState.scrolledOutside && (
|
|
|
- <button
|
|
|
- className="scroll-back-to-content"
|
|
|
- onClick={() => {
|
|
|
- setAppState({ ...calculateScrollCenter(elements) });
|
|
|
- }}
|
|
|
- >
|
|
|
- {t("buttons.scrollBackToContent")}
|
|
|
- </button>
|
|
|
+ {appState.multiElement ? (
|
|
|
+ <>
|
|
|
+ {actionManager.renderAction("deleteSelectedElements")}
|
|
|
+ <ToolButton
|
|
|
+ visible={showSelectedShapeActions}
|
|
|
+ type="button"
|
|
|
+ icon={palette}
|
|
|
+ aria-label={t("buttons.edit")}
|
|
|
+ onClick={() =>
|
|
|
+ setAppState(({ openedMenu }: any) => ({
|
|
|
+ openedMenu: openedMenu === "shape" ? null : "shape",
|
|
|
+ }))
|
|
|
+ }
|
|
|
+ />
|
|
|
+ {actionManager.renderAction("finalize")}
|
|
|
+ </>
|
|
|
+ ) : (
|
|
|
+ <>
|
|
|
+ <ToolButton
|
|
|
+ type="button"
|
|
|
+ icon={menu}
|
|
|
+ aria-label={t("buttons.menu")}
|
|
|
+ onClick={() =>
|
|
|
+ setAppState(({ openedMenu }: any) => ({
|
|
|
+ openedMenu: openedMenu === "canvas" ? null : "canvas",
|
|
|
+ }))
|
|
|
+ }
|
|
|
+ />
|
|
|
+ <ToolButton
|
|
|
+ visible={showSelectedShapeActions}
|
|
|
+ type="button"
|
|
|
+ icon={palette}
|
|
|
+ aria-label={t("buttons.edit")}
|
|
|
+ onClick={() =>
|
|
|
+ setAppState(({ openedMenu }: any) => ({
|
|
|
+ openedMenu: openedMenu === "shape" ? null : "shape",
|
|
|
+ }))
|
|
|
+ }
|
|
|
+ />
|
|
|
+ {actionManager.renderAction("deleteSelectedElements")}
|
|
|
+ {appState.scrolledOutside && (
|
|
|
+ <button
|
|
|
+ className="scroll-back-to-content"
|
|
|
+ onClick={() => {
|
|
|
+ setAppState({ ...calculateScrollCenter(elements) });
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {t("buttons.scrollBackToContent")}
|
|
|
+ </button>
|
|
|
+ )}
|
|
|
+ </>
|
|
|
)}
|
|
|
</div>
|
|
|
</footer>
|
|
@@ -545,7 +540,7 @@ const LayerUI = React.memo(
|
|
|
</Stack.Col>
|
|
|
</Island>
|
|
|
</section>
|
|
|
- {showSelectedShapeActions ? (
|
|
|
+ {showSelectedShapeActions && (
|
|
|
<section
|
|
|
className="App-right-menu"
|
|
|
aria-labelledby="selected-shape-title"
|
|
@@ -555,7 +550,7 @@ const LayerUI = React.memo(
|
|
|
</h2>
|
|
|
<Island padding={4}>{renderSelectedShapeActions()}</Island>
|
|
|
</section>
|
|
|
- ) : null}
|
|
|
+ )}
|
|
|
</Stack.Col>
|
|
|
<section aria-labelledby="shapes-title">
|
|
|
<Stack.Col gap={4} align="start">
|
|
@@ -566,7 +561,19 @@ const LayerUI = React.memo(
|
|
|
</h2>
|
|
|
<Stack.Row gap={1}>{renderShapesSwitcher()}</Stack.Row>
|
|
|
</Island>
|
|
|
- {lockButton}
|
|
|
+ <LockIcon
|
|
|
+ checked={appState.elementLocked}
|
|
|
+ onChange={() => {
|
|
|
+ setAppState({
|
|
|
+ elementLocked: !appState.elementLocked,
|
|
|
+ elementType: appState.elementLocked
|
|
|
+ ? "selection"
|
|
|
+ : appState.elementType,
|
|
|
+ });
|
|
|
+ }}
|
|
|
+ title={t("toolBar.lock")}
|
|
|
+ isButton={isMobile}
|
|
|
+ />
|
|
|
</Stack.Row>
|
|
|
</Stack.Col>
|
|
|
</section>
|
|
@@ -591,6 +598,7 @@ const LayerUI = React.memo(
|
|
|
}}
|
|
|
languages={languages}
|
|
|
currentLanguage={language}
|
|
|
+ floating
|
|
|
/>
|
|
|
{appState.scrolledOutside && (
|
|
|
<button
|
|
@@ -1085,6 +1093,8 @@ export class App extends React.Component<any, AppState> {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
+ this.setState({ lastPointerDownWith: e.pointerType });
|
|
|
+
|
|
|
// pan canvas on wheel button drag or space+drag
|
|
|
if (
|
|
|
gesture.pointers.length === 0 &&
|
|
@@ -1213,6 +1223,7 @@ export class App extends React.Component<any, AppState> {
|
|
|
elements,
|
|
|
{ x, y },
|
|
|
this.state.zoom,
|
|
|
+ e.pointerType,
|
|
|
);
|
|
|
|
|
|
const selectedElements = getSelectedElements(elements);
|
|
@@ -1279,6 +1290,9 @@ export class App extends React.Component<any, AppState> {
|
|
|
if (this.state.editingElement?.type === "text") {
|
|
|
return;
|
|
|
}
|
|
|
+ if (elementIsAddedToSelection) {
|
|
|
+ element = hitElement!;
|
|
|
+ }
|
|
|
let textX = e.clientX;
|
|
|
let textY = e.clientY;
|
|
|
if (!e.altKey) {
|
|
@@ -2152,6 +2166,7 @@ export class App extends React.Component<any, AppState> {
|
|
|
elements,
|
|
|
{ x, y },
|
|
|
this.state.zoom,
|
|
|
+ e.pointerType,
|
|
|
);
|
|
|
if (resizeElement && resizeElement.resizeHandle) {
|
|
|
document.documentElement.style.cursor = getCursorForResizingElement(
|