@ -104,6 +104,7 @@ import { LanguageList } from "./components/LanguageList";
@@ -104,6 +104,7 @@ import { LanguageList } from "./components/LanguageList";
import { Point } from "roughjs/bin/geometry" ;
import { t , languages , setLanguage , getLanguage } from "./i18n" ;
import { HintViewer } from "./components/HintViewer" ;
import useIsMobile , { IsMobileProvider } from "./is-mobile" ;
import { copyToAppClipboard , getClipboardContent } from "./clipboard" ;
import { normalizeScroll } from "./scene/data" ;
@ -135,6 +136,18 @@ const MOUSE_BUTTON = {
@@ -135,6 +136,18 @@ const MOUSE_BUTTON = {
SECONDARY : 2 ,
} ;
// Block pinch-zooming on iOS outside of the content area
document . addEventListener (
"touchmove" ,
function ( event ) {
// @ts-ignore
if ( event . scale !== 1 ) {
event . preventDefault ( ) ;
}
} ,
{ passive : false } ,
) ;
let lastMouseUp : ( ( e : any ) = > void ) | null = null ;
export function viewportCoordsToSceneCoords (
@ -211,12 +224,10 @@ const LayerUI = React.memo(
@@ -211,12 +224,10 @@ const LayerUI = React.memo(
language ,
setElements ,
} : LayerUIProps ) = > {
function renderCanvasActions() {
const isMobile = useIsMobile ( ) ;
function renderExportDialog() {
return (
< Stack.Col gap = { 4 } >
< Stack.Row justifyContent = { "space-between" } >
{ actionManager . renderAction ( "loadScene" ) }
{ actionManager . renderAction ( "saveScene" ) }
< ExportDialog
elements = { elements }
appState = { appState }
@ -265,10 +276,6 @@ const LayerUI = React.memo(
@@ -265,10 +276,6 @@ const LayerUI = React.memo(
}
} }
/ >
{ actionManager . renderAction ( "clearCanvas" ) }
< / Stack.Row >
{ actionManager . renderAction ( "changeViewBackgroundColor" ) }
< / Stack.Col >
) ;
}
@ -284,7 +291,6 @@ const LayerUI = React.memo(
@@ -284,7 +291,6 @@ const LayerUI = React.memo(
}
return (
< Island padding = { 4 } >
< div className = "panelColumn" >
{ actionManager . renderAction ( "changeStrokeColor" ) }
{ ( hasBackground ( elementType ) ||
@ -328,7 +334,6 @@ const LayerUI = React.memo(
@@ -328,7 +334,6 @@ const LayerUI = React.memo(
{ actionManager . renderAction ( "deleteSelectedElements" ) }
< / div >
< / Island >
) ;
}
@ -378,7 +383,125 @@ const LayerUI = React.memo(
@@ -378,7 +383,125 @@ const LayerUI = React.memo(
) ;
}
return (
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" ? (
< section
className = "App-mobile-menu"
aria - labelledby = "canvas-actions-title"
>
< h2 className = "visually-hidden" id = "canvas-actions-title" >
{ t ( "headings.canvasActions" ) }
< / h2 >
< div className = "App-mobile-menu-scroller" >
< Stack.Col gap = { 4 } >
{ actionManager . renderAction ( "loadScene" ) }
{ actionManager . renderAction ( "saveScene" ) }
{ renderExportDialog ( ) }
{ actionManager . renderAction ( "clearCanvas" ) }
{ actionManager . renderAction ( "changeViewBackgroundColor" ) }
< / Stack.Col >
< / div >
< / section >
) : appState . openedMenu === "shape" ? (
< section
className = "App-mobile-menu"
aria - labelledby = "selected-shape-title"
>
< h2 className = "visually-hidden" id = "selected-shape-title" >
{ t ( "headings.selectedShapeActions" ) }
< / h2 >
< div className = "App-mobile-menu-scroller" >
{ renderSelectedShapeActions ( elements ) }
< / div >
< / section >
) : null }
< FixedSideContainer side = "top" >
< section aria - labelledby = "shapes-title" >
< Stack.Col gap = { 4 } align = "center" >
< Stack.Row gap = { 1 } >
< Island padding = { 1 } >
< h2 className = "visually-hidden" id = "shapes-title" >
{ t ( "headings.shapes" ) }
< / h2 >
< Stack.Row gap = { 1 } > { renderShapesSwitcher ( ) } < / Stack.Row >
< / Island >
< / Stack.Row >
< / Stack.Col >
< / section >
< / 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" ,
} ) )
}
/ >
{ lockButton }
< 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 >
< HintViewer
elementType = { appState . elementType }
multiMode = { appState . multiElement !== null }
isResizing = { appState . isResizing }
elements = { elements }
/ >
{ appState . scrolledOutside && (
< button
className = "scroll-back-to-content"
onClick = { ( ) = > {
setAppState ( { . . . calculateScrollCenter ( elements ) } ) ;
} }
>
{ t ( "buttons.scrollBackToContent" ) }
< / button >
) }
< / div >
< / footer >
< / >
) : (
< >
< FixedSideContainer side = "top" >
< div className = "App-menu App-menu_top" >
@ -390,7 +513,17 @@ const LayerUI = React.memo(
@@ -390,7 +513,17 @@ const LayerUI = React.memo(
< h2 className = "visually-hidden" id = "canvas-actions-title" >
{ t ( "headings.canvasActions" ) }
< / h2 >
< Island padding = { 4 } > { renderCanvasActions ( ) } < / Island >
< Island padding = { 4 } >
< Stack.Col gap = { 4 } >
< Stack.Row justifyContent = { "space-between" } >
{ actionManager . renderAction ( "loadScene" ) }
{ actionManager . renderAction ( "saveScene" ) }
{ renderExportDialog ( ) }
{ actionManager . renderAction ( "clearCanvas" ) }
< / Stack.Row >
{ actionManager . renderAction ( "changeViewBackgroundColor" ) }
< / Stack.Col >
< / Island >
< / section >
< section
className = "App-right-menu"
@ -399,7 +532,9 @@ const LayerUI = React.memo(
@@ -399,7 +532,9 @@ const LayerUI = React.memo(
< h2 className = "visually-hidden" id = "selected-shape-title" >
{ t ( "headings.selectedShapeActions" ) }
< / h2 >
< Island padding = { 4 } >
{ renderSelectedShapeActions ( elements ) }
< / Island >
< / section >
< / Stack.Col >
< section aria - labelledby = "shapes-title" >
@ -411,18 +546,7 @@ const LayerUI = React.memo(
@@ -411,18 +546,7 @@ const LayerUI = React.memo(
< / h2 >
< Stack.Row gap = { 1 } > { renderShapesSwitcher ( ) } < / Stack.Row >
< / Island >
< LockIcon
checked = { appState . elementLocked }
onChange = { ( ) = > {
setAppState ( {
elementLocked : ! appState . elementLocked ,
elementType : appState.elementLocked
? "selection"
: appState . elementType ,
} ) ;
} }
title = { t ( "toolBar.lock" ) }
/ >
{ lockButton }
< / Stack.Row >
< / Stack.Col >
< / section >
@ -2204,7 +2328,9 @@ class TopErrorBoundary extends React.Component {
@@ -2204,7 +2328,9 @@ class TopErrorBoundary extends React.Component {
ReactDOM . render (
< TopErrorBoundary >
< IsMobileProvider >
< App / >
< / IsMobileProvider >
< / TopErrorBoundary > ,
rootElement ,
) ;