@@ -390,7 +513,17 @@ const LayerUI = React.memo(
{t("headings.canvasActions")}
-
{renderCanvasActions()}
+
+
+
+ {actionManager.renderAction("loadScene")}
+ {actionManager.renderAction("saveScene")}
+ {renderExportDialog()}
+ {actionManager.renderAction("clearCanvas")}
+
+ {actionManager.renderAction("changeViewBackgroundColor")}
+
+
{t("headings.selectedShapeActions")}
- {renderSelectedShapeActions(elements)}
+
+ {renderSelectedShapeActions(elements)}
+
@@ -411,18 +546,7 @@ const LayerUI = React.memo(
{renderShapesSwitcher()}
- {
- setAppState({
- elementLocked: !appState.elementLocked,
- elementType: appState.elementLocked
- ? "selection"
- : appState.elementType,
- });
- }}
- title={t("toolBar.lock")}
- />
+ {lockButton}
@@ -2204,7 +2328,9 @@ class TopErrorBoundary extends React.Component {
ReactDOM.render(
-
+
+
+
,
rootElement,
);
diff --git a/src/is-mobile.tsx b/src/is-mobile.tsx
new file mode 100644
index 00000000..e0cf092b
--- /dev/null
+++ b/src/is-mobile.tsx
@@ -0,0 +1,25 @@
+import React, { useState, useEffect, useRef, useContext } from "react";
+
+const context = React.createContext(false);
+
+export function IsMobileProvider({ children }: { children: React.ReactNode }) {
+ const query = useRef
();
+ if (!query.current) {
+ query.current = window.matchMedia(
+ "(max-width: 600px), (max-height: 500px)",
+ );
+ }
+ const [isMobile, setMobile] = useState(query.current.matches);
+
+ useEffect(() => {
+ const handler = () => setMobile(query.current!.matches);
+ query.current!.addListener(handler);
+ return () => query.current!.removeListener(handler);
+ }, []);
+
+ return {children};
+}
+
+export default function useIsMobile() {
+ return useContext(context);
+}
diff --git a/src/locales/en.json b/src/locales/en.json
index e6088437..02af6931 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -43,7 +43,7 @@
"layers": "Layers"
},
"buttons": {
- "clearReset": "Clear the canvas & reset background color",
+ "clearReset": "Reset the canvas",
"export": "Export",
"exportToPng": "Export to PNG",
"exportToSvg": "Export to SVG",
@@ -55,7 +55,8 @@
"selectLanguage": "Select Language",
"scrollBackToContent": "Scroll back to content",
"zoomIn": "Zoom in",
- "zoomOut": "Zoom out"
+ "zoomOut": "Zoom out",
+ "menu": "Menu"
},
"alerts": {
"clearReset": "This will clear the whole canvas. Are you sure?",
diff --git a/src/styles.scss b/src/styles.scss
index 17cbf318..93c32144 100644
--- a/src/styles.scss
+++ b/src/styles.scss
@@ -2,11 +2,16 @@
body {
margin: 0;
- font-family: Arial, Helvetica, sans-serif;
+ --ui-font: Arial, Helvetica, sans-serif;
+ font-family: var(--ui-font);
color: var(--text-color-primary);
+ -webkit-text-size-adjust: 100%;
}
canvas {
+ touch-action: none;
+ user-select: none;
+
// following props improve blurriness at certain devicePixelRatios.
// AFAIK it doesn't affect export (in fact, export seems sharp either way).
@@ -24,6 +29,11 @@ canvas {
right: 0;
}
+.panelRow {
+ display: flex;
+ justify-content: space-between;
+}
+
.panelColumn {
display: flex;
flex-direction: column;
@@ -91,6 +101,7 @@ input:focus {
button,
.buttonList label {
+ user-select: none;
background-color: #e9ecef;
border: 0;
border-radius: 4px;
@@ -128,6 +139,47 @@ button,
}
}
+.App-toolbar {
+ padding: var(--spacing);
+ padding-bottom: #{"max(var(--spacing), env(safe-area-inset-bottom))"};
+ padding-left: #{"max(var(--spacing), env(safe-area-inset-left))"};
+ padding-right: #{"max(var(--spacing), env(safe-area-inset-right))"};
+ width: 100%;
+ box-sizing: border-box;
+ overflow: auto;
+ position: absolute;
+ bottom: 0;
+}
+.App-toolbar-content {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+.App-toolbar,
+.App-mobile-menu {
+ --spacing: 0.5rem;
+ background: #fcfcfc;
+ border-top: 1px solid #ccc;
+}
+.App-mobile-menu {
+ --bottom: calc(3rem - 1px + max(var(--spacing), env(safe-area-inset-bottom)));
+ display: grid;
+ position: fixed;
+ width: 100%;
+ bottom: var(--bottom);
+ z-index: 4;
+ max-height: calc(100% - var(--bottom));
+ overflow-y: scroll;
+}
+.App-mobile-menu .App-mobile-menu-scroller {
+ background: #fcfcfc;
+ box-shadow: none;
+ --padding: calc(4 * var(--space-factor));
+ padding: var(--padding);
+ padding-left: #{"max(var(--padding), env(safe-area-inset-left))"};
+ padding-right: #{"max(var(--padding), env(safe-area-inset-right))"};
+}
+
.App-menu {
display: grid;
}
@@ -303,3 +355,13 @@ button,
transform: translateX(-50%);
padding: 10px 20px;
}
+
+@media (max-width: 600px), (max-height: 500px) {
+ aside {
+ display: none;
+ }
+ .scroll-back-to-content {
+ bottom: 70px;
+ bottom: calc(70px + env(safe-area-inset-bottom));
+ }
+}
diff --git a/src/types.ts b/src/types.ts
index 5c8f4433..308e93ad 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -31,4 +31,5 @@ export type AppState = {
selectedId?: string;
isResizing: boolean;
zoom: number;
+ openedMenu: "canvas" | "shape" | null;
};