- Changes the brush color in dark mode
- Fixes the paste button
- Fixes a bug with pasting on mobile
- Adds a Sign Out button to the menu
- Hides the status bar in production
- Adds debug mode to preferences
- Refactors style panel
- Hides keyboard shortcuts when not on mobile
pull/44/head
Steve Ruiz 2021-07-11 13:51:01 +01:00
rodzic 293edd7683
commit 74ff10bfd5
16 zmienionych plików z 274 dodań i 265 usunięć

Wyświetl plik

@ -15,6 +15,7 @@ import Coop from './coop/coop'
import Brush from './brush'
import Defs from './defs'
import Page from './page'
import useSafariFocusOutFix from 'hooks/useSafariFocusOutFix'
function resetError() {
null
@ -28,6 +29,8 @@ export default function Canvas(): JSX.Element {
useZoomEvents()
useSafariFocusOutFix()
const events = useCanvasEvents(rCanvas)
const isReady = useSelector((s) => s.isIn('ready'))
@ -62,9 +65,10 @@ const MainSVG = styled('svg', {
height: '100%',
touchAction: 'none',
zIndex: 100,
backgroundColor: '$canvas',
pointerEvents: 'all',
// cursor: 'none',
backgroundColor: '$canvas',
borderTop: '1px solid $border',
borderBottom: '1px solid $border',
'& *': {
userSelect: 'none',

Wyświetl plik

@ -12,7 +12,7 @@ import {
ContextMenuRoot,
MenuContent,
} from 'components/shared'
import { commandKey, deepCompareArrays, isMobile } from 'utils'
import { commandKey, deepCompareArrays } from 'utils'
import state, { useSelector } from 'state'
import {
AlignType,
@ -36,6 +36,7 @@ import {
StretchHorizontallyIcon,
StretchVerticallyIcon,
} from '@radix-ui/react-icons'
import { Kbd } from 'components/shared'
function alignTop() {
state.send('ALIGNED', { type: AlignType.Top })
@ -101,34 +102,30 @@ export default function ContextMenu({
return (
<ContextMenuRoot>
<_ContextMenu.Trigger>{children}</_ContextMenu.Trigger>
<MenuContent
as={_ContextMenu.Content}
ref={rContent}
isMobile={isMobile()}
>
<MenuContent as={_ContextMenu.Content} ref={rContent}>
{selectedShapeIds.length ? (
<>
{/* <ContextMenuButton onSelect={() => state.send('COPIED')}>
<span>Copy</span>
<kbd>
<Kbd>
<span>{commandKey()}</span>
<span>C</span>
</kbd>
</Kbd>
</ContextMenuButton>
<ContextMenuButton onSelect={() => state.send('CUT')}>
<span>Cut</span>
<kbd>
<Kbd>
<span>{commandKey()}</span>
<span>X</span>
</kbd>
</Kbd>
</ContextMenuButton>
*/}
<ContextMenuButton onSelect={() => state.send('DUPLICATED')}>
<span>Duplicate</span>
<kbd>
<Kbd>
<span>{commandKey()}</span>
<span>D</span>
</kbd>
</Kbd>
</ContextMenuButton>
<ContextMenuDivider />
{hasGroupSelected ||
@ -137,20 +134,20 @@ export default function ContextMenu({
{hasGroupSelected && (
<ContextMenuButton onSelect={() => state.send('UNGROUPED')}>
<span>Ungroup</span>
<kbd>
<Kbd>
<span>{commandKey()}</span>
<span></span>
<span>G</span>
</kbd>
</Kbd>
</ContextMenuButton>
)}
{hasTwoOrMore && (
<ContextMenuButton onSelect={() => state.send('GROUPED')}>
<span>Group</span>
<kbd>
<Kbd>
<span>{commandKey()}</span>
<span>G</span>
</kbd>
</Kbd>
</ContextMenuButton>
)}
</>
@ -164,11 +161,11 @@ export default function ContextMenu({
}
>
<span>To Front</span>
<kbd>
<Kbd>
<span>{commandKey()}</span>
<span></span>
<span>]</span>
</kbd>
</Kbd>
</ContextMenuButton>
<ContextMenuButton
@ -179,10 +176,10 @@ export default function ContextMenu({
}
>
<span>Forward</span>
<kbd>
<Kbd>
<span>{commandKey()}</span>
<span>]</span>
</kbd>
</Kbd>
</ContextMenuButton>
<ContextMenuButton
onSelect={() =>
@ -192,10 +189,10 @@ export default function ContextMenu({
}
>
<span>Backward</span>
<kbd>
<Kbd>
<span>{commandKey()}</span>
<span>[</span>
</kbd>
</Kbd>
</ContextMenuButton>
<ContextMenuButton
onSelect={() =>
@ -205,11 +202,11 @@ export default function ContextMenu({
}
>
<span>To Back</span>
<kbd>
<Kbd>
<span>{commandKey()}</span>
<span></span>
<span>[</span>
</kbd>
</Kbd>
</ContextMenuButton>
</ContextMenuSubMenu>
{hasTwoOrMore && (
@ -221,36 +218,36 @@ export default function ContextMenu({
<MoveToPageMenu />
<ContextMenuButton onSelect={() => state.send('COPIED_TO_SVG')}>
<span>Copy to SVG</span>
<kbd>
<Kbd>
<span>{commandKey()}</span>
<span></span>
<span>C</span>
</kbd>
</Kbd>
</ContextMenuButton>
<ContextMenuDivider />
<ContextMenuButton onSelect={() => state.send('DELETED')}>
<span>Delete</span>
<kbd>
<Kbd>
<span></span>
</kbd>
</Kbd>
</ContextMenuButton>
</>
) : (
<>
<ContextMenuButton onSelect={() => state.send('UNDO')}>
<span>Undo</span>
<kbd>
<Kbd>
<span>{commandKey()}</span>
<span>Z</span>
</kbd>
</Kbd>
</ContextMenuButton>
<ContextMenuButton onSelect={() => state.send('REDO')}>
<span>Redo</span>
<kbd>
<Kbd>
<span>{commandKey()}</span>
<span></span>
<span>Z</span>
</kbd>
</Kbd>
</ContextMenuButton>
</>
)}
@ -277,7 +274,6 @@ function AlignDistributeSubMenu({
as={_ContextMenu.Content}
sideOffset={2}
alignOffset={-2}
isMobile={isMobile()}
selectedStyle={hasThreeOrMore ? 'threeOrMore' : 'twoOrMore'}
>
<ContextMenuIconButton onSelect={alignLeft}>
@ -355,12 +351,7 @@ function MoveToPageMenu() {
<ChevronRightIcon />
</IconWrapper>
</ContextMenuButton>
<MenuContent
as={_ContextMenu.Content}
sideOffset={2}
alignOffset={-2}
isMobile={isMobile()}
>
<MenuContent as={_ContextMenu.Content} sideOffset={2} alignOffset={-2}>
{sorted.map(({ id, name }) => (
<ContextMenuButton
key={id}

Wyświetl plik

@ -2,14 +2,9 @@ import { useSelector } from 'state'
import { ShapeTreeNode } from 'types'
import ShapeComponent from './shape'
/*
On each state change, populate a tree structure with all of
the shapes that we need to render..
*/
export default function Page(): JSX.Element {
// Get a tree of shapes to render
const shapesToRender = useSelector((s) => s.values.shapesToRender)
const allowHovers = useSelector((s) =>
s.isInAny('selecting', 'text', 'editingShape')
)

Wyświetl plik

@ -2,8 +2,13 @@
import styled from 'styles'
import React, { useRef } from 'react'
import state, { useSelector } from 'state'
import * as Panel from '../panel'
import { breakpoints, IconButton, RowButton, IconWrapper } from '../shared'
import * as Panel from 'components/panel'
import {
breakpoints,
IconButton,
RowButton,
IconWrapper,
} from 'components/shared'
import {
Cross2Icon,
PlayIcon,
@ -179,7 +184,6 @@ export default function CodePanel(): JSX.Element {
}
const StylePanelRoot = styled(Panel.Root, {
marginRight: '8px',
width: 'fit-content',
maxWidth: 'fit-content',
overflow: 'hidden',

Wyświetl plik

@ -2,7 +2,6 @@ import useKeyboardEvents from 'hooks/useKeyboardEvents'
import useLoadOnMount from 'hooks/useLoadOnMount'
import Menu from './menu/menu'
import Canvas from './canvas/canvas'
import StatusBar from './status-bar'
import ToolsPanel from './tools-panel/tools-panel'
import StylePanel from './style-panel/style-panel'
import styled from 'styles'
@ -28,7 +27,6 @@ export default function Editor({ roomId }: { roomId?: string }): JSX.Element {
<StylePanel />
<Canvas />
<ToolsPanel />
<StatusBar />
</Layout>
)
}
@ -56,6 +54,8 @@ const Layout = styled('main', {
display: 'flex',
alignItems: 'flex-start',
justifyContent: 'flex-start',
boxSizing: 'border-box',
pointerEvents: 'none',
'& > *': {
PointerEvent: 'all',

Wyświetl plik

@ -1,5 +1,5 @@
import * as React from 'react'
import { HamburgerMenuIcon } from '@radix-ui/react-icons'
import { ExitIcon, HamburgerMenuIcon } from '@radix-ui/react-icons'
import { Trigger, Content } from '@radix-ui/react-dropdown-menu'
import { memo } from 'react'
import {
@ -12,14 +12,18 @@ import {
DropdownMenuSubMenu,
DropdownMenuDivider,
DropdownMenuCheckboxItem,
IconWrapper,
Kbd,
} from '../shared'
import state, { useSelector } from 'state'
import { commandKey } from 'utils'
import { signOut } from 'next-auth/client'
const handleNew = () => state.send('CREATED_NEW_PROJECT')
const handleSave = () => state.send('SAVED')
const handleLoad = () => state.send('LOADED_FROM_FILE_STSTEM')
const toggleDarkMode = () => state.send('TOGGLED_DARK_MODE')
const toggleDebugMode = () => state.send('TOGGLED_DEBUG_MODE')
function Menu() {
return (
@ -31,38 +35,45 @@ function Menu() {
<Content as={MenuContent} sideOffset={8}>
<DropdownMenuButton onSelect={handleNew} disabled>
<span>New Project</span>
<kbd>
<Kbd>
<span>{commandKey()}</span>
<span>N</span>
</kbd>
</Kbd>
</DropdownMenuButton>
<DropdownMenuDivider />
<DropdownMenuButton onSelect={handleLoad}>
<span>Open...</span>
<kbd>
<Kbd>
<span>{commandKey()}</span>
<span>L</span>
</kbd>
</Kbd>
</DropdownMenuButton>
<RecentFiles />
<DropdownMenuDivider />
<DropdownMenuButton onSelect={handleSave}>
<span>Save</span>
<kbd>
<Kbd>
<span>{commandKey()}</span>
<span>S</span>
</kbd>
</Kbd>
</DropdownMenuButton>
<DropdownMenuButton onSelect={handleSave}>
<span>Save As...</span>
<kbd>
<Kbd>
<span></span>
<span>{commandKey()}</span>
<span>S</span>
</kbd>
</Kbd>
</DropdownMenuButton>
<DropdownMenuDivider />
<Preferences />
<DropdownMenuDivider />
<DropdownMenuButton onSelect={signOut}>
<span>Sign Out</span>
<IconWrapper size="small">
<ExitIcon />
</IconWrapper>
</DropdownMenuButton>
</Content>
</DropdownMenuRoot>
</FloatingContainer>
@ -88,6 +99,7 @@ function RecentFiles() {
}
function Preferences() {
const isDebugMode = useSelector((s) => s.data.settings.isDebugMode)
const isDarkMode = useSelector((s) => s.data.settings.isDarkMode)
return (
@ -98,6 +110,12 @@ function Preferences() {
>
<span>Dark Mode</span>
</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem
checked={isDebugMode}
onCheckedChange={toggleDebugMode}
>
<span>Debug Mode</span>
</DropdownMenuCheckboxItem>
</DropdownMenuSubMenu>
)
}

Wyświetl plik

@ -3,7 +3,7 @@ import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import * as RadioGroup from '@radix-ui/react-radio-group'
import * as Panel from './panel'
import styled from 'styles'
import { forwardRef } from 'react'
import React, { forwardRef } from 'react'
import { CheckIcon, ChevronRightIcon } from '@radix-ui/react-icons'
import { isMobile } from 'utils'
@ -466,6 +466,14 @@ export const FloatingContainer = styled('div', {
zIndex: 200,
variants: {
direction: {
row: {
flexDirection: 'row',
},
column: {
flexDirection: 'column',
},
},
elevation: {
0: {
boxShadow: 'none',
@ -483,6 +491,23 @@ export const FloatingContainer = styled('div', {
},
})
export const StyledKbd = styled('kbd', {
marginLeft: '32px',
fontSize: '$1',
fontFamily: '$ui',
fontWeight: 400,
'& > span': {
display: 'inline-block',
width: '12px',
},
})
export function Kbd({ children }: { children: React.ReactNode }): JSX.Element {
if (isMobile()) return null
return <StyledKbd>{children}</StyledKbd>
}
/* -------------------------------------------------- */
/* Menus */
/* -------------------------------------------------- */
@ -498,30 +523,17 @@ export const MenuContent = styled('div', {
border: '1px solid $panel',
padding: '$0',
boxShadow: '$4',
minWidth: 200,
minWidth: 180,
font: '$ui',
})
'& kbd': {
marginLeft: '32px',
fontSize: '$1',
fontFamily: '$ui',
fontWeight: 400,
},
'& kbd > span': {
display: 'inline-block',
width: '12px',
},
variants: {
isMobile: {
true: {
'& kbd': {
display: 'none',
},
},
},
},
export const Divider = styled('div', {
backgroundColor: '$hover',
height: 1,
marginTop: '$2',
marginRight: '-$2',
marginBottom: '$2',
marginLeft: '-$2',
})
/* -------------------------------------------------- */
@ -565,12 +577,7 @@ export function DropdownMenuSubMenu({
<ChevronRightIcon />
</IconWrapper>
</RowButton>
<MenuContent
as={DropdownMenu.Content}
sideOffset={2}
alignOffset={-2}
isMobile={isMobile()}
>
<MenuContent as={DropdownMenu.Content} sideOffset={2} alignOffset={-2}>
{children}
<DropdownMenuArrow offset={13} />
</MenuContent>
@ -699,12 +706,7 @@ export function ContextMenuSubMenu({
<ChevronRightIcon />
</IconWrapper>
</ContextMenu.TriggerItem>
<ContextMenu.Content
as={MenuContent}
sideOffset={2}
alignOffset={-2}
isMobile={isMobile()}
>
<ContextMenu.Content as={MenuContent} sideOffset={2} alignOffset={-2}>
{children}
<ContextMenuArrow offset={13} />
</ContextMenu.Content>

Wyświetl plik

@ -17,6 +17,8 @@ export default function StatusBar(): JSX.Element {
const log = local.log[0]
if (process.env.NODE_ENV === 'development') return null
return (
<StatusBarContainer size={size}>
<Section>
@ -28,11 +30,6 @@ export default function StatusBar(): JSX.Element {
}
const StatusBarContainer = styled('div', {
position: 'absolute',
bottom: 0,
left: 0,
width: '100%',
zIndex: 300,
height: 40,
userSelect: 'none',
borderTop: '1px solid $border',

Wyświetl plik

@ -1,13 +1,12 @@
import styled from 'styles'
import state, { useSelector } from 'state'
import * as Panel from 'components/panel'
import { useRef } from 'react'
import {
IconButton,
IconWrapper,
ButtonsRow,
RowButton,
breakpoints,
RowButton,
FloatingContainer,
Divider,
Kbd,
} from 'components/shared'
import ShapesFunctions from './shapes-functions'
import AlignDistribute from './align-distribute'
@ -16,14 +15,8 @@ import QuickSizeSelect from './quick-size-select'
import QuickDashSelect from './quick-dash-select'
import QuickFillSelect from './quick-fill-select'
import Tooltip from 'components/tooltip'
import { motion } from 'framer-motion'
import {
ClipboardCopyIcon,
ClipboardIcon,
DotsHorizontalIcon,
Share2Icon,
Cross2Icon,
} from '@radix-ui/react-icons'
import { DotsHorizontalIcon, Cross2Icon } from '@radix-ui/react-icons'
import { commandKey, isMobile } from 'utils'
const handleStylePanelOpen = () => state.send('TOGGLED_STYLE_PANEL_OPEN')
const handleCopy = () => state.send('COPIED')
@ -31,12 +24,10 @@ const handlePaste = () => state.send('PASTED')
const handleCopyToSvg = () => state.send('COPIED_TO_SVG')
export default function StylePanel(): JSX.Element {
const rContainer = useRef<HTMLDivElement>(null)
const isOpen = useSelector((s) => s.data.settings.isStyleOpen)
return (
<StylePanelRoot dir="ltr" ref={rContainer} isOpen={isOpen}>
<FloatingContainer direction="column">
<ButtonsRow>
<QuickColorSelect />
<QuickSizeSelect />
@ -54,84 +45,57 @@ export default function StylePanel(): JSX.Element {
</IconButton>
</ButtonsRow>
{isOpen && <SelectedShapeContent />}
</StylePanelRoot>
</FloatingContainer>
)
}
function SelectedShapeContent(): JSX.Element {
const selectedShapesCount = useSelector((s) => s.values.selectedIds.length)
const showKbds = !isMobile()
return (
<>
<hr />
<Divider />
<ShapesFunctions />
<hr />
<Divider />
<AlignDistribute
hasTwoOrMore={selectedShapesCount > 1}
hasThreeOrMore={selectedShapesCount > 2}
/>
<hr />
<Divider />
<RowButton
bp={breakpoints}
disabled={selectedShapesCount === 0}
onClick={handleCopy}
>
<span>Copy</span>
<IconWrapper size="small">
<ClipboardCopyIcon />
</IconWrapper>
{showKbds && (
<Kbd>
<span>{commandKey()}</span>
<span>C</span>
</Kbd>
)}
</RowButton>
<RowButton bp={breakpoints} onClick={handlePaste}>
<span>Paste</span>
<IconWrapper size="small">
<ClipboardIcon />
</IconWrapper>
{showKbds && (
<Kbd>
<span>{commandKey()}</span>
<span>V</span>
</Kbd>
)}
</RowButton>
<RowButton
bp={breakpoints}
disabled={selectedShapesCount === 0}
onClick={handleCopyToSvg}
>
<RowButton bp={breakpoints} onClick={handleCopyToSvg}>
<span>Copy to SVG</span>
<IconWrapper size="small">
<Share2Icon />
</IconWrapper>
{showKbds && (
<Kbd>
<span></span>
<span>{commandKey()}</span>
<span>C</span>
</Kbd>
)}
</RowButton>
</>
)
}
const StylePanelRoot = styled(motion(Panel.Root), {
minWidth: 1,
width: 'fit-content',
maxWidth: 'fit-content',
overflow: 'hidden',
position: 'relative',
border: '1px solid $panel',
boxShadow: '$4',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
pointerEvents: 'all',
padding: '$0',
zIndex: 300,
'& hr': {
marginTop: 2,
marginBottom: 2,
marginLeft: '-$0',
border: 'none',
height: 1,
backgroundColor: '$brushFill',
width: 'calc(100% + 4px)',
},
variants: {
isOpen: {
true: {},
false: {
width: 'fit-content',
},
},
},
})

Wyświetl plik

@ -8,10 +8,11 @@ import {
SquareIcon,
TextIcon,
} from '@radix-ui/react-icons'
import { PrimaryButton, SecondaryButton } from './shared'
import { FloatingContainer } from '../shared'
import React from 'react'
import * as React from 'react'
import state, { useSelector } from 'state'
import StatusBar from 'components/status-bar'
import { FloatingContainer } from 'components/shared'
import { PrimaryButton, SecondaryButton } from './shared'
import styled from 'styles'
import { ShapeType } from 'types'
import UndoRedo from './undo-redo'
@ -97,13 +98,16 @@ export default function ToolsPanel(): JSX.Element {
</FloatingContainer>
<UndoRedo />
</RightWrap>
<StatusWrap>
<StatusBar />
</StatusWrap>
</ToolsPanelContainer>
)
}
const ToolsPanelContainer = styled('div', {
position: 'fixed',
bottom: 44,
bottom: 0,
left: 0,
right: 0,
width: '100%',
@ -111,10 +115,11 @@ const ToolsPanelContainer = styled('div', {
maxWidth: '100%',
display: 'grid',
gridTemplateColumns: '1fr auto 1fr',
padding: '0 8px 12px 8px',
padding: '0',
alignItems: 'flex-end',
zIndex: 200,
gap: 12,
gridGap: '$4',
gridRowGap: '$4',
})
const CenterWrap = styled('div', {
@ -132,6 +137,7 @@ const LeftWrap = styled('div', {
gridRow: 1,
gridColumn: 1,
display: 'flex',
paddingLeft: '$3',
variants: {
size: {
mobile: {
@ -158,6 +164,7 @@ const RightWrap = styled('div', {
gridRow: 1,
gridColumn: 3,
display: 'flex',
paddingRight: '$3',
variants: {
size: {
mobile: {
@ -179,3 +186,8 @@ const RightWrap = styled('div', {
},
},
})
const StatusWrap = styled('div', {
gridRow: 2,
gridColumn: '1 / span 3',
})

Wyświetl plik

@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { MutableRefObject, useCallback, useEffect } from 'react'
import { MutableRefObject, useCallback } from 'react'
import state from 'state'
import {
fastBrushSelect,
@ -9,87 +9,80 @@ import {
fastTranslate,
} from 'state/hacks'
import inputs from 'state/inputs'
import { isMobile } from 'utils'
import Vec from 'utils/vec'
function handleFocusOut() {
state.send('BLURRED_EDITING_SHAPE')
}
export default function useCanvasEvents(
rCanvas: MutableRefObject<SVGGElement>
) {
const handlePointerDown = useCallback((e: React.PointerEvent) => {
if (!inputs.canAccept(e.pointerId)) return
const handlePointerDown = useCallback(
(e: React.PointerEvent<SVGSVGElement>) => {
if (!inputs.canAccept(e.pointerId)) return
rCanvas.current.setPointerCapture(e.pointerId)
rCanvas.current.setPointerCapture(e.pointerId)
const info = inputs.pointerDown(e, 'canvas')
const info = inputs.pointerDown(e, 'canvas')
if (e.button === 0) {
if (inputs.isDoubleClick() && !(info.altKey || info.metaKey)) {
state.send('DOUBLE_POINTED_CANVAS', info)
if (e.button === 0) {
if (inputs.isDoubleClick() && !(info.altKey || info.metaKey)) {
state.send('DOUBLE_POINTED_CANVAS', info)
}
state.send('POINTED_CANVAS', info)
} else if (e.button === 2) {
state.send('RIGHT_POINTED', info)
}
},
[]
)
const handlePointerMove = useCallback(
(e: React.PointerEvent<SVGSVGElement>) => {
if (!inputs.canAccept(e.pointerId)) return
const prev = inputs.pointer?.point
const info = inputs.pointerMove(e)
if (prev && state.isIn('selecting') && inputs.keys[' ']) {
const delta = Vec.sub(prev, info.point)
fastPanUpdate(delta)
state.send('KEYBOARD_PANNED_CAMERA', {
delta: Vec.sub(prev, info.point),
})
return
}
state.send('POINTED_CANVAS', info)
} else if (e.button === 2) {
state.send('RIGHT_POINTED', info)
}
}, [])
const handlePointerMove = useCallback((e: React.PointerEvent) => {
if (!inputs.canAccept(e.pointerId)) return
const prev = inputs.pointer?.point
const info = inputs.pointerMove(e)
if (prev && state.isIn('selecting') && inputs.keys[' ']) {
const delta = Vec.sub(prev, info.point)
fastPanUpdate(delta)
state.send('KEYBOARD_PANNED_CAMERA', { delta: Vec.sub(prev, info.point) })
return
}
if (state.isIn('draw.editing')) {
fastDrawUpdate(info)
} else if (state.isIn('brushSelecting')) {
fastBrushSelect(info.point)
} else if (state.isIn('translatingSelection')) {
fastTranslate(info)
} else if (state.isIn('transformingSelection')) {
fastTransform(info)
}
state.send('MOVED_POINTER', info)
}, [])
const handlePointerUp = useCallback((e: React.PointerEvent) => {
if (!inputs.canAccept(e.pointerId)) return
rCanvas.current.releasePointerCapture(e.pointerId)
state.send('STOPPED_POINTING', {
id: 'canvas',
...inputs.pointerUp(e, 'canvas'),
})
}, [])
const handleTouchStart = useCallback(() => {
// if (isMobile()) {
// if (e.touches.length === 2) {
// state.send('TOUCH_UNDO')
// } else state.send('TOUCHED_CANVAS')
// }
}, [])
// Send event on iOS when a user presses the "Done" key while editing a text element
useEffect(() => {
if (isMobile()) {
document.addEventListener('focusout', handleFocusOut)
return () => {
document.removeEventListener('focusout', handleFocusOut)
if (state.isIn('draw.editing')) {
fastDrawUpdate(info)
} else if (state.isIn('brushSelecting')) {
fastBrushSelect(info.point)
} else if (state.isIn('translatingSelection')) {
fastTranslate(info)
} else if (state.isIn('transformingSelection')) {
fastTransform(info)
}
state.send('MOVED_POINTER', info)
},
[]
)
const handlePointerUp = useCallback(
(e: React.PointerEvent<SVGSVGElement>) => {
if (!inputs.canAccept(e.pointerId)) return
rCanvas.current.releasePointerCapture(e.pointerId)
state.send('STOPPED_POINTING', {
id: 'canvas',
...inputs.pointerUp(e, 'canvas'),
})
},
[]
)
const handleTouchStart = useCallback((e: React.TouchEvent<SVGSVGElement>) => {
if ('safari' in window) {
e.preventDefault()
}
}, [])

Wyświetl plik

@ -195,11 +195,7 @@ export default function useKeyboardEvents() {
}
case 'd': {
if (metaKey(e)) {
if (e.shiftKey) {
state.send('TOGGLED_DEBUG_MODE')
} else {
state.send('DUPLICATED', info)
}
state.send('DUPLICATED', info)
} else {
state.send('SELECTED_DRAW_TOOL', info)
}

Wyświetl plik

@ -0,0 +1,22 @@
import isMobile from 'ismobilejs'
import { useEffect } from 'react'
import state from 'state'
// Send event on iOS when a user presses the "Done" key while editing
// a text element.
function handleFocusOut() {
state.send('BLURRED_EDITING_SHAPE')
}
export default function useSafariFocusOutFix(): void {
useEffect(() => {
if (isMobile().apple) {
document.addEventListener('focusout', handleFocusOut)
return () => {
document.removeEventListener('focusout', handleFocusOut)
}
}
}, [])
}

Wyświetl plik

@ -9,6 +9,8 @@ class Clipboard {
fallback = false
copy = (shapes: Shape[], onComplete?: () => void) => {
if (shapes === undefined) return
this.current = JSON.stringify({ id: 'tldr', shapes })
if ('permissions' in navigator && 'clipboard' in navigator) {
@ -37,7 +39,7 @@ class Clipboard {
return this
}
sendPastedTextToState(text = this.current) {
sendPastedTextToState = (text = this.current) => {
if (text === undefined) return
try {
@ -62,10 +64,13 @@ class Clipboard {
copySelectionToSvg(data: Data) {
const shapes = tld.getSelectedShapes(data)
const shapesToCopy = shapes.length > 0 ? shapes : tld.getShapes(data)
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
shapes
if (shapesToCopy.length === 0) return
shapesToCopy
.sort((a, b) => a.childIndex - b.childIndex)
.forEach((shape) => {
const group = document.getElementById(shape.id)
@ -78,7 +83,7 @@ class Clipboard {
})
const bounds = getCommonBounds(
...shapes.map((shape) => getShapeUtils(shape).getBounds(shape))
...shapesToCopy.map((shape) => getShapeUtils(shape).getBounds(shape))
)
// No content

Wyświetl plik

@ -2137,8 +2137,13 @@ const state = createState({
pasteFromClipboard(data) {
clipboard.paste()
if (clipboard.fallback) {
commands.paste(data, JSON.parse(clipboard.current).shapes)
try {
commands.paste(data, JSON.parse(clipboard.current).shapes)
} catch (e) {
console.warn('Could not paste that text.')
}
}
},
@ -2245,16 +2250,15 @@ const state = createState({
return commonStyle
},
shapesToRender(data) {
const viewport = tld.getViewport(data)
const page = tld.getPage(data)
const currentShapes = Object.values(page.shapes)
.filter((shape) => shape.parentId === page.id)
.sort((a, b) => a.childIndex - b.childIndex)
const shapesToShow = Object.values(page.shapes).filter((shape) => {
if (shape.parentId !== page.id) return false
const shapesToShow = currentShapes.filter((shape) => {
const shapeBounds = getShapeUtils(shape).getBounds(shape)
return (
@ -2270,9 +2274,9 @@ const state = createState({
const selectedIds = tld.getSelectedIds(data)
shapesToShow.forEach((shape) =>
tld.addToShapeTree(data, selectedIds, tree, shape)
)
shapesToShow
.sort((a, b) => a.childIndex - b.childIndex)
.forEach((shape) => tld.addToShapeTree(data, selectedIds, tree, shape))
return tree
},

Wyświetl plik

@ -39,6 +39,7 @@ const { styled, global, css, theme, getCssString } = createCss({
1: '3px',
2: '4px',
3: '8px',
4: '12px',
},
fontSizes: {
0: '10px',
@ -96,8 +97,8 @@ const light = theme({})
const dark = theme({
colors: {
brushFill: 'rgba(0,0,0,.05)',
brushStroke: 'rgba(0,0,0,.25)',
brushFill: 'rgba(180, 180, 180, .05)',
brushStroke: 'rgba(180, 180, 180, .25)',
hint: 'rgba(216, 226, 249, 1.000)',
selected: 'rgba(38, 150, 255, 1.000)',
bounds: 'rgba(38, 150, 255, 1.000)',
@ -136,6 +137,7 @@ const globalStyles = global({
padding: '0px',
margin: '0px',
overscrollBehavior: 'none',
overscrollBehaviorX: 'none',
fontFamily: '$ui',
fontSize: '$2',
color: '$text',