Improves file saving / page saving and loading
This commit is contained in:
parent
7e03adcd52
commit
4ce2b8cc6b
68 changed files with 1166 additions and 997 deletions
|
@ -4,7 +4,7 @@ import { useRef } from 'react'
|
||||||
import { useSelector } from 'state'
|
import { useSelector } from 'state'
|
||||||
import styled from 'styles'
|
import styled from 'styles'
|
||||||
import { deepCompareArrays, getPage } from 'utils/utils'
|
import { deepCompareArrays, getPage } from 'utils/utils'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
|
|
||||||
export default function Handles() {
|
export default function Handles() {
|
||||||
const selectedIds = useSelector(
|
const selectedIds = useSelector(
|
||||||
|
|
|
@ -2,7 +2,6 @@ import styled from 'styles'
|
||||||
import { useSelector } from 'state'
|
import { useSelector } from 'state'
|
||||||
import {
|
import {
|
||||||
deepCompareArrays,
|
deepCompareArrays,
|
||||||
getBoundsCenter,
|
|
||||||
getPage,
|
getPage,
|
||||||
getSelectedIds,
|
getSelectedIds,
|
||||||
setToArray,
|
setToArray,
|
||||||
|
@ -11,7 +10,7 @@ import { getShapeUtils } from 'lib/shape-utils'
|
||||||
import useShapeEvents from 'hooks/useShapeEvents'
|
import useShapeEvents from 'hooks/useShapeEvents'
|
||||||
import { memo, useRef } from 'react'
|
import { memo, useRef } from 'react'
|
||||||
import { ShapeType } from 'types'
|
import { ShapeType } from 'types'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
|
|
||||||
export default function Selected() {
|
export default function Selected() {
|
||||||
const currentSelectedShapeIds = useSelector(
|
const currentSelectedShapeIds = useSelector(
|
||||||
|
@ -69,6 +68,7 @@ const SelectIndicator = styled('path', {
|
||||||
strokeLinejoin: 'round',
|
strokeLinejoin: 'round',
|
||||||
stroke: '$selected',
|
stroke: '$selected',
|
||||||
pointerEvents: 'none',
|
pointerEvents: 'none',
|
||||||
|
fill: 'none',
|
||||||
|
|
||||||
variants: {
|
variants: {
|
||||||
isLocked: {
|
isLocked: {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { getShapeUtils } from 'lib/shape-utils'
|
||||||
import { getBoundsCenter, getPage } from 'utils/utils'
|
import { getBoundsCenter, getPage } from 'utils/utils'
|
||||||
import { ShapeStyles, ShapeType } from 'types'
|
import { ShapeStyles, ShapeType } from 'types'
|
||||||
import useShapeEvents from 'hooks/useShapeEvents'
|
import useShapeEvents from 'hooks/useShapeEvents'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
import { getShapeStyle } from 'lib/shape-styles'
|
import { getShapeStyle } from 'lib/shape-styles'
|
||||||
import ContextMenu from 'components/context-menu'
|
import ContextMenu from 'components/context-menu'
|
||||||
|
|
||||||
|
@ -30,7 +30,6 @@ function Shape({ id, isSelecting, parentPoint }: ShapeProps) {
|
||||||
// detects the change and pulls this component.
|
// detects the change and pulls this component.
|
||||||
if (!shape) return null
|
if (!shape) return null
|
||||||
|
|
||||||
const utils = getShapeUtils(shape)
|
|
||||||
const style = getShapeStyle(shape.style)
|
const style = getShapeStyle(shape.style)
|
||||||
const shapeUtils = getShapeUtils(shape)
|
const shapeUtils = getShapeUtils(shape)
|
||||||
const { isShy, isParent, isForeignObject } = shapeUtils
|
const { isShy, isParent, isForeignObject } = shapeUtils
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import * as _ContextMenu from '@radix-ui/react-context-menu'
|
import * as _ContextMenu from '@radix-ui/react-context-menu'
|
||||||
import * as _Dropdown from '@radix-ui/react-dropdown-menu'
|
import * as _Dropdown from '@radix-ui/react-dropdown-menu'
|
||||||
import styled from 'styles'
|
import styled from 'styles'
|
||||||
import { RowButton } from './shared'
|
import { IconWrapper, RowButton } from './shared'
|
||||||
import {
|
import {
|
||||||
commandKey,
|
commandKey,
|
||||||
deepCompareArrays,
|
deepCompareArrays,
|
||||||
|
@ -11,6 +11,7 @@ import {
|
||||||
import state, { useSelector } from 'state'
|
import state, { useSelector } from 'state'
|
||||||
import { MoveType, ShapeType } from 'types'
|
import { MoveType, ShapeType } from 'types'
|
||||||
import React, { useRef } from 'react'
|
import React, { useRef } from 'react'
|
||||||
|
import { ChevronRightIcon } from '@radix-ui/react-icons'
|
||||||
|
|
||||||
export default function ContextMenu({
|
export default function ContextMenu({
|
||||||
children,
|
children,
|
||||||
|
@ -56,65 +57,9 @@ export default function ContextMenu({
|
||||||
</kbd>
|
</kbd>
|
||||||
</Button>
|
</Button>
|
||||||
<StyledDivider />
|
<StyledDivider />
|
||||||
<Button
|
|
||||||
onSelect={() =>
|
|
||||||
state.send('MOVED', {
|
|
||||||
type: MoveType.ToFront,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<span>Move To Front</span>
|
|
||||||
<kbd>
|
|
||||||
<span>{commandKey()}</span>
|
|
||||||
<span>⇧</span>
|
|
||||||
<span>]</span>
|
|
||||||
</kbd>
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
onSelect={() =>
|
|
||||||
state.send('MOVED', {
|
|
||||||
type: MoveType.Forward,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<span>Move Forward</span>
|
|
||||||
<kbd>
|
|
||||||
<span>{commandKey()}</span>
|
|
||||||
<span>]</span>
|
|
||||||
</kbd>
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onSelect={() =>
|
|
||||||
state.send('MOVED', {
|
|
||||||
type: MoveType.Backward,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<span>Move Backward</span>
|
|
||||||
<kbd>
|
|
||||||
<span>{commandKey()}</span>
|
|
||||||
<span>[</span>
|
|
||||||
</kbd>
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onSelect={() =>
|
|
||||||
state.send('MOVED', {
|
|
||||||
type: MoveType.ToBack,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<span>Move to Back</span>
|
|
||||||
<kbd>
|
|
||||||
<span>{commandKey()}</span>
|
|
||||||
<span>⇧</span>
|
|
||||||
<span>[</span>
|
|
||||||
</kbd>
|
|
||||||
</Button>
|
|
||||||
{hasGroupSelectd ||
|
{hasGroupSelectd ||
|
||||||
(hasMultipleSelected && (
|
(hasMultipleSelected && (
|
||||||
<>
|
<>
|
||||||
<StyledDivider />
|
|
||||||
{hasGroupSelectd && (
|
{hasGroupSelectd && (
|
||||||
<Button onSelect={() => state.send('UNGROUPED')}>
|
<Button onSelect={() => state.send('UNGROUPED')}>
|
||||||
<span>Ungroup</span>
|
<span>Ungroup</span>
|
||||||
|
@ -136,11 +81,65 @@ export default function ContextMenu({
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
))}
|
))}
|
||||||
<StyledDivider />
|
<SubMenu label="Move">
|
||||||
|
<Button
|
||||||
|
onSelect={() =>
|
||||||
|
state.send('MOVED', {
|
||||||
|
type: MoveType.ToFront,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span>To Front</span>
|
||||||
|
<kbd>
|
||||||
|
<span>{commandKey()}</span>
|
||||||
|
<span>⇧</span>
|
||||||
|
<span>]</span>
|
||||||
|
</kbd>
|
||||||
|
</Button>
|
||||||
|
|
||||||
{/* <Button onSelect={() => state.send('MOVED_TO_PAGE')}> */}
|
<Button
|
||||||
<MoveToPageDropDown>Move to Page</MoveToPageDropDown>
|
onSelect={() =>
|
||||||
{/* </Button> */}
|
state.send('MOVED', {
|
||||||
|
type: MoveType.Forward,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span>Forward</span>
|
||||||
|
<kbd>
|
||||||
|
<span>{commandKey()}</span>
|
||||||
|
<span>]</span>
|
||||||
|
</kbd>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onSelect={() =>
|
||||||
|
state.send('MOVED', {
|
||||||
|
type: MoveType.Backward,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span>Backward</span>
|
||||||
|
<kbd>
|
||||||
|
<span>{commandKey()}</span>
|
||||||
|
<span>[</span>
|
||||||
|
</kbd>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onSelect={() =>
|
||||||
|
state.send('MOVED', {
|
||||||
|
type: MoveType.ToBack,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span>To Back</span>
|
||||||
|
<kbd>
|
||||||
|
<span>{commandKey()}</span>
|
||||||
|
<span>⇧</span>
|
||||||
|
<span>[</span>
|
||||||
|
</kbd>
|
||||||
|
</Button>
|
||||||
|
</SubMenu>
|
||||||
|
<MoveToPageMenu />
|
||||||
|
<StyledDivider />
|
||||||
<Button onSelect={() => state.send('DELETED')}>
|
<Button onSelect={() => state.send('DELETED')}>
|
||||||
<span>Delete</span>
|
<span>Delete</span>
|
||||||
<kbd>
|
<kbd>
|
||||||
|
@ -180,8 +179,7 @@ const StyledContent = styled(_ContextMenu.Content, {
|
||||||
pointerEvents: 'all',
|
pointerEvents: 'all',
|
||||||
userSelect: 'none',
|
userSelect: 'none',
|
||||||
zIndex: 200,
|
zIndex: 200,
|
||||||
padding: 2,
|
padding: 3,
|
||||||
border: '1px solid $panel',
|
|
||||||
boxShadow: '0px 2px 4px rgba(0,0,0,.2)',
|
boxShadow: '0px 2px 4px rgba(0,0,0,.2)',
|
||||||
minWidth: 128,
|
minWidth: 128,
|
||||||
|
|
||||||
|
@ -210,7 +208,7 @@ const StyledContent = styled(_ContextMenu.Content, {
|
||||||
const StyledDivider = styled(_ContextMenu.Separator, {
|
const StyledDivider = styled(_ContextMenu.Separator, {
|
||||||
backgroundColor: '$hover',
|
backgroundColor: '$hover',
|
||||||
height: 1,
|
height: 1,
|
||||||
margin: '2px -2px',
|
margin: '3px -3px',
|
||||||
})
|
})
|
||||||
|
|
||||||
function Button({
|
function Button({
|
||||||
|
@ -234,7 +232,33 @@ function Button({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function MoveToPageDropDown({ children }: { children: React.ReactNode }) {
|
function SubMenu({
|
||||||
|
children,
|
||||||
|
label,
|
||||||
|
}: {
|
||||||
|
label: string
|
||||||
|
children: React.ReactNode
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<_ContextMenu.Root>
|
||||||
|
<_ContextMenu.TriggerItem
|
||||||
|
as={RowButton}
|
||||||
|
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||||
|
>
|
||||||
|
<span>{label}</span>
|
||||||
|
<IconWrapper size="small">
|
||||||
|
<ChevronRightIcon />
|
||||||
|
</IconWrapper>
|
||||||
|
</_ContextMenu.TriggerItem>
|
||||||
|
<StyledContent sideOffset={2} alignOffset={-2} isMobile={isMobile()}>
|
||||||
|
{children}
|
||||||
|
<StyledArrow offset={13} />
|
||||||
|
</StyledContent>
|
||||||
|
</_ContextMenu.Root>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function MoveToPageMenu() {
|
||||||
const documentPages = useSelector((s) => s.data.document.pages)
|
const documentPages = useSelector((s) => s.data.document.pages)
|
||||||
const currentPageId = useSelector((s) => s.data.currentPageId)
|
const currentPageId = useSelector((s) => s.data.currentPageId)
|
||||||
|
|
||||||
|
@ -245,27 +269,29 @@ function MoveToPageDropDown({ children }: { children: React.ReactNode }) {
|
||||||
.filter((a) => a.id !== currentPageId)
|
.filter((a) => a.id !== currentPageId)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<_Dropdown.Root>
|
<_ContextMenu.Root>
|
||||||
<_Dropdown.Trigger
|
<_ContextMenu.TriggerItem
|
||||||
as={RowButton}
|
as={RowButton}
|
||||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||||
>
|
>
|
||||||
{children}
|
<span>Move To Page</span>
|
||||||
</_Dropdown.Trigger>
|
<IconWrapper size="small">
|
||||||
<StyledDialogContent side="right" sideOffset={8}>
|
<ChevronRightIcon />
|
||||||
|
</IconWrapper>
|
||||||
|
</_ContextMenu.TriggerItem>
|
||||||
|
<StyledContent sideOffset={2} alignOffset={-2} isMobile={isMobile()}>
|
||||||
{sorted.map(({ id, name }) => (
|
{sorted.map(({ id, name }) => (
|
||||||
<_Dropdown.Item
|
<Button
|
||||||
as={RowButton}
|
|
||||||
key={id}
|
key={id}
|
||||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
|
||||||
disabled={id === currentPageId}
|
disabled={id === currentPageId}
|
||||||
onSelect={() => state.send('MOVED_TO_PAGE', { id })}
|
onSelect={() => state.send('MOVED_TO_PAGE', { id })}
|
||||||
>
|
>
|
||||||
<span>{name}</span>
|
<span>{name}</span>
|
||||||
</_Dropdown.Item>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</StyledDialogContent>
|
<StyledArrow offset={13} />
|
||||||
</_Dropdown.Root>
|
</StyledContent>
|
||||||
|
</_ContextMenu.Root>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -293,3 +319,7 @@ const StyledDialogContent = styled(_Dropdown.Content, {
|
||||||
outline: 'none',
|
outline: 'none',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const StyledArrow = styled(_ContextMenu.Arrow, {
|
||||||
|
fill: 'white',
|
||||||
|
})
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { strokes } from 'lib/shape-styles'
|
||||||
import { ColorStyle } from 'types'
|
import { ColorStyle } from 'types'
|
||||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||||
import { Square } from 'react-feather'
|
import { Square } from 'react-feather'
|
||||||
import styled from 'styles'
|
|
||||||
import { DropdownContent } from '../shared'
|
import { DropdownContent } from '../shared'
|
||||||
|
|
||||||
export default function ColorContent({
|
export default function ColorContent({
|
||||||
|
|
|
@ -8,12 +8,9 @@ import {
|
||||||
import * as RadioGroup from '@radix-ui/react-radio-group'
|
import * as RadioGroup from '@radix-ui/react-radio-group'
|
||||||
import { DashStyle } from 'types'
|
import { DashStyle } from 'types'
|
||||||
import state from 'state'
|
import state from 'state'
|
||||||
import { ChangeEvent } from 'react'
|
|
||||||
|
|
||||||
function handleChange(e: ChangeEvent<HTMLInputElement>) {
|
function handleChange(dash: string) {
|
||||||
state.send('CHANGED_STYLE', {
|
state.send('CHANGED_STYLE', { dash })
|
||||||
dash: e.currentTarget.value,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { IconWrapper, RowButton } from '../shared'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isFilled: boolean
|
isFilled: boolean
|
||||||
onChange: (isFilled: boolean) => void
|
onChange: (isFilled: boolean | string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function IsFilledPicker({ isFilled, onChange }: Props) {
|
export default function IsFilledPicker({ isFilled, onChange }: Props) {
|
||||||
|
@ -15,9 +15,7 @@ export default function IsFilledPicker({ isFilled, onChange }: Props) {
|
||||||
as={RowButton}
|
as={RowButton}
|
||||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||||
checked={isFilled}
|
checked={isFilled}
|
||||||
onCheckedChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
onCheckedChange={onChange}
|
||||||
onChange(e.currentTarget.checked)
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<label htmlFor="fill">Fill</label>
|
<label htmlFor="fill">Fill</label>
|
||||||
<IconWrapper>
|
<IconWrapper>
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
import { Group, Item } from '../shared'
|
import { Group, Item } from '../shared'
|
||||||
import * as RadioGroup from '@radix-ui/react-radio-group'
|
import * as RadioGroup from '@radix-ui/react-radio-group'
|
||||||
import { ChangeEvent } from 'react'
|
|
||||||
import { Circle } from 'react-feather'
|
import { Circle } from 'react-feather'
|
||||||
import state from 'state'
|
import state from 'state'
|
||||||
import { SizeStyle } from 'types'
|
import { SizeStyle } from 'types'
|
||||||
|
|
||||||
function handleChange(e: ChangeEvent<HTMLInputElement>) {
|
function handleChange(size: string) {
|
||||||
state.send('CHANGED_STYLE', {
|
state.send('CHANGED_STYLE', { size })
|
||||||
size: e.currentTarget.value as SizeStyle,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SizePicker({ size }: { size: SizeStyle }) {
|
export default function SizePicker({ size }: { size: SizeStyle }) {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
|
import { fastTransform } from 'state/hacks'
|
||||||
import inputs from 'state/inputs'
|
import inputs from 'state/inputs'
|
||||||
import { Edge, Corner } from 'types'
|
import { Edge, Corner } from 'types'
|
||||||
|
|
||||||
|
@ -29,7 +30,15 @@ export default function useBoundsEvents(handle: Edge | Corner | 'rotate') {
|
||||||
if (e.buttons !== 1) return
|
if (e.buttons !== 1) return
|
||||||
if (!inputs.canAccept(e.pointerId)) return
|
if (!inputs.canAccept(e.pointerId)) return
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
state.send('MOVED_POINTER', inputs.pointerMove(e))
|
|
||||||
|
const info = inputs.pointerMove(e)
|
||||||
|
|
||||||
|
if (state.isIn('transformingSelection')) {
|
||||||
|
fastTransform(info)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
state.send('MOVED_POINTER', info)
|
||||||
},
|
},
|
||||||
[handle]
|
[handle]
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React, { useEffect } from 'react'
|
import React, { useEffect } from 'react'
|
||||||
import state from 'state'
|
import state from 'state'
|
||||||
|
import storage from 'state/storage'
|
||||||
import { getCurrentCamera } from 'utils/utils'
|
import { getCurrentCamera } from 'utils/utils'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -23,10 +24,7 @@ export default function useCamera(ref: React.MutableRefObject<SVGGElement>) {
|
||||||
`scale(${zoom}) translate(${point[0]} ${point[1]})`
|
`scale(${zoom}) translate(${point[0]} ${point[1]})`
|
||||||
)
|
)
|
||||||
|
|
||||||
localStorage.setItem(
|
storage.savePageState(state.data)
|
||||||
'code_slate_camera',
|
|
||||||
JSON.stringify({ point, zoom })
|
|
||||||
)
|
|
||||||
|
|
||||||
prev = getCurrentCamera(state.data)
|
prev = getCurrentCamera(state.data)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
import { MutableRefObject, useCallback } from 'react'
|
import { MutableRefObject, useCallback } from 'react'
|
||||||
import state from 'state'
|
import state from 'state'
|
||||||
import { fastBrushSelect, fastDrawUpdate, fastTranslate } from 'state/hacks'
|
import {
|
||||||
|
fastBrushSelect,
|
||||||
|
fastDrawUpdate,
|
||||||
|
fastTransform,
|
||||||
|
fastTranslate,
|
||||||
|
} from 'state/hacks'
|
||||||
import inputs from 'state/inputs'
|
import inputs from 'state/inputs'
|
||||||
import { isMobile } from 'utils/utils'
|
import { isMobile } from 'utils/utils'
|
||||||
|
|
||||||
|
@ -47,6 +52,11 @@ export default function useCanvasEvents(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state.isIn('transformingSelection')) {
|
||||||
|
fastTransform(info)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
state.send('MOVED_POINTER', info)
|
state.send('MOVED_POINTER', info)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { useEffect, useRef } from 'react'
|
import React, { useEffect, useRef } from 'react'
|
||||||
import state from 'state'
|
import state from 'state'
|
||||||
import inputs from 'state/inputs'
|
import inputs from 'state/inputs'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
import { useGesture } from 'react-use-gesture'
|
import { useGesture } from 'react-use-gesture'
|
||||||
import {
|
import {
|
||||||
fastBrushSelect,
|
fastBrushSelect,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import CodeShape from './index'
|
import CodeShape from './index'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { uniqueId } from 'utils/utils'
|
||||||
import { CircleShape, ShapeType } from 'types'
|
import { CircleShape, ShapeType } from 'types'
|
||||||
import { vectorToPoint } from 'utils/utils'
|
import { vectorToPoint } from 'utils/utils'
|
||||||
import { defaultStyle } from 'lib/shape-styles'
|
import { defaultStyle } from 'lib/shape-styles'
|
||||||
|
@ -9,7 +9,7 @@ export default class Circle extends CodeShape<CircleShape> {
|
||||||
props.point = vectorToPoint(props.point)
|
props.point = vectorToPoint(props.point)
|
||||||
|
|
||||||
super({
|
super({
|
||||||
id: uuid(),
|
id: uniqueId(),
|
||||||
seed: Math.random(),
|
seed: Math.random(),
|
||||||
parentId: (window as any).currentPageId,
|
parentId: (window as any).currentPageId,
|
||||||
type: ShapeType.Circle,
|
type: ShapeType.Circle,
|
||||||
|
|
|
@ -3,8 +3,8 @@ import {
|
||||||
ControlType,
|
ControlType,
|
||||||
NumberCodeControl,
|
NumberCodeControl,
|
||||||
VectorCodeControl,
|
VectorCodeControl,
|
||||||
} from "types"
|
} from 'types'
|
||||||
import { v4 as uuid } from "uuid"
|
import { v4 as uuid } from 'uuid'
|
||||||
|
|
||||||
export const controls: Record<string, any> = {}
|
export const controls: Record<string, any> = {}
|
||||||
|
|
||||||
|
@ -13,8 +13,8 @@ export const codeControls = new Set<CodeControl>([])
|
||||||
export class Control<T extends CodeControl> {
|
export class Control<T extends CodeControl> {
|
||||||
control: T
|
control: T
|
||||||
|
|
||||||
constructor(control: Omit<T, "id">) {
|
constructor(control: Omit<T, 'id'>) {
|
||||||
this.control = { ...control, id: uuid() } as T
|
this.control = { ...control, id: uniqueId() } as T
|
||||||
codeControls.add(this.control)
|
codeControls.add(this.control)
|
||||||
|
|
||||||
// Could there be a better way to prevent this?
|
// Could there be a better way to prevent this?
|
||||||
|
@ -32,7 +32,7 @@ export class Control<T extends CodeControl> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NumberControl extends Control<NumberCodeControl> {
|
export class NumberControl extends Control<NumberCodeControl> {
|
||||||
constructor(options: Omit<NumberCodeControl, "id" | "type">) {
|
constructor(options: Omit<NumberCodeControl, 'id' | 'type'>) {
|
||||||
const { value = 0, step = 1 } = options
|
const { value = 0, step = 1 } = options
|
||||||
super({
|
super({
|
||||||
type: ControlType.Number,
|
type: ControlType.Number,
|
||||||
|
@ -44,7 +44,7 @@ export class NumberControl extends Control<NumberCodeControl> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class VectorControl extends Control<VectorCodeControl> {
|
export class VectorControl extends Control<VectorCodeControl> {
|
||||||
constructor(options: Omit<VectorCodeControl, "id" | "type">) {
|
constructor(options: Omit<VectorCodeControl, 'id' | 'type'>) {
|
||||||
const { value = [0, 0], isNormalized = false } = options
|
const { value = [0, 0], isNormalized = false } = options
|
||||||
super({
|
super({
|
||||||
type: ControlType.Vector,
|
type: ControlType.Vector,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import CodeShape from './index'
|
import CodeShape from './index'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { uniqueId } from 'utils/utils'
|
||||||
import { DotShape, ShapeType } from 'types'
|
import { DotShape, ShapeType } from 'types'
|
||||||
import { vectorToPoint } from 'utils/utils'
|
import { vectorToPoint } from 'utils/utils'
|
||||||
import { defaultStyle } from 'lib/shape-styles'
|
import { defaultStyle } from 'lib/shape-styles'
|
||||||
|
@ -9,7 +9,7 @@ export default class Dot extends CodeShape<DotShape> {
|
||||||
props.point = vectorToPoint(props.point)
|
props.point = vectorToPoint(props.point)
|
||||||
|
|
||||||
super({
|
super({
|
||||||
id: uuid(),
|
id: uniqueId(),
|
||||||
seed: Math.random(),
|
seed: Math.random(),
|
||||||
parentId: (window as any).currentPageId,
|
parentId: (window as any).currentPageId,
|
||||||
type: ShapeType.Dot,
|
type: ShapeType.Dot,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import CodeShape from './index'
|
import CodeShape from './index'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { uniqueId } from 'utils/utils'
|
||||||
import { EllipseShape, ShapeType } from 'types'
|
import { EllipseShape, ShapeType } from 'types'
|
||||||
import { vectorToPoint } from 'utils/utils'
|
import { vectorToPoint } from 'utils/utils'
|
||||||
import { defaultStyle } from 'lib/shape-styles'
|
import { defaultStyle } from 'lib/shape-styles'
|
||||||
|
@ -9,7 +9,7 @@ export default class Ellipse extends CodeShape<EllipseShape> {
|
||||||
props.point = vectorToPoint(props.point)
|
props.point = vectorToPoint(props.point)
|
||||||
|
|
||||||
super({
|
super({
|
||||||
id: uuid(),
|
id: uniqueId(),
|
||||||
seed: Math.random(),
|
seed: Math.random(),
|
||||||
parentId: (window as any).currentPageId,
|
parentId: (window as any).currentPageId,
|
||||||
type: ShapeType.Ellipse,
|
type: ShapeType.Ellipse,
|
||||||
|
|
|
@ -4,7 +4,7 @@ import shapeUtilityMap, {
|
||||||
getShapeUtils,
|
getShapeUtils,
|
||||||
ShapeUtility,
|
ShapeUtility,
|
||||||
} from 'lib/shape-utils'
|
} from 'lib/shape-utils'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
import Vector from './vector'
|
import Vector from './vector'
|
||||||
import { vectorToPoint } from 'utils/utils'
|
import { vectorToPoint } from 'utils/utils'
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import CodeShape from './index'
|
import CodeShape from './index'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { uniqueId } from 'utils/utils'
|
||||||
import { LineShape, ShapeType } from 'types'
|
import { LineShape, ShapeType } from 'types'
|
||||||
import { vectorToPoint } from 'utils/utils'
|
import { vectorToPoint } from 'utils/utils'
|
||||||
import { defaultStyle } from 'lib/shape-styles'
|
import { defaultStyle } from 'lib/shape-styles'
|
||||||
|
@ -10,7 +10,7 @@ export default class Line extends CodeShape<LineShape> {
|
||||||
props.direction = vectorToPoint(props.direction)
|
props.direction = vectorToPoint(props.direction)
|
||||||
|
|
||||||
super({
|
super({
|
||||||
id: uuid(),
|
id: uniqueId(),
|
||||||
seed: Math.random(),
|
seed: Math.random(),
|
||||||
parentId: (window as any).currentPageId,
|
parentId: (window as any).currentPageId,
|
||||||
type: ShapeType.Line,
|
type: ShapeType.Line,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import CodeShape from './index'
|
import CodeShape from './index'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { uniqueId } from 'utils/utils'
|
||||||
import { PolylineShape, ShapeType } from 'types'
|
import { PolylineShape, ShapeType } from 'types'
|
||||||
import { vectorToPoint } from 'utils/utils'
|
import { vectorToPoint } from 'utils/utils'
|
||||||
import { defaultStyle } from 'lib/shape-styles'
|
import { defaultStyle } from 'lib/shape-styles'
|
||||||
|
@ -10,7 +10,7 @@ export default class Polyline extends CodeShape<PolylineShape> {
|
||||||
props.points = props.points.map(vectorToPoint)
|
props.points = props.points.map(vectorToPoint)
|
||||||
|
|
||||||
super({
|
super({
|
||||||
id: uuid(),
|
id: uniqueId(),
|
||||||
seed: Math.random(),
|
seed: Math.random(),
|
||||||
parentId: (window as any).currentPageId,
|
parentId: (window as any).currentPageId,
|
||||||
type: ShapeType.Polyline,
|
type: ShapeType.Polyline,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import CodeShape from './index'
|
import CodeShape from './index'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { uniqueId } from 'utils/utils'
|
||||||
import { RayShape, ShapeType } from 'types'
|
import { RayShape, ShapeType } from 'types'
|
||||||
import { vectorToPoint } from 'utils/utils'
|
import { vectorToPoint } from 'utils/utils'
|
||||||
import { defaultStyle } from 'lib/shape-styles'
|
import { defaultStyle } from 'lib/shape-styles'
|
||||||
|
@ -10,7 +10,7 @@ export default class Ray extends CodeShape<RayShape> {
|
||||||
props.direction = vectorToPoint(props.direction)
|
props.direction = vectorToPoint(props.direction)
|
||||||
|
|
||||||
super({
|
super({
|
||||||
id: uuid(),
|
id: uniqueId(),
|
||||||
seed: Math.random(),
|
seed: Math.random(),
|
||||||
type: ShapeType.Ray,
|
type: ShapeType.Ray,
|
||||||
isGenerated: true,
|
isGenerated: true,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import CodeShape from './index'
|
import CodeShape from './index'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { uniqueId } from 'utils/utils'
|
||||||
import { RectangleShape, ShapeType } from 'types'
|
import { RectangleShape, ShapeType } from 'types'
|
||||||
import { vectorToPoint } from 'utils/utils'
|
import { vectorToPoint } from 'utils/utils'
|
||||||
import { defaultStyle } from 'lib/shape-styles'
|
import { defaultStyle } from 'lib/shape-styles'
|
||||||
|
@ -10,7 +10,7 @@ export default class Rectangle extends CodeShape<RectangleShape> {
|
||||||
props.size = vectorToPoint(props.size)
|
props.size = vectorToPoint(props.size)
|
||||||
|
|
||||||
super({
|
super({
|
||||||
id: uuid(),
|
id: uniqueId(),
|
||||||
seed: Math.random(),
|
seed: Math.random(),
|
||||||
parentId: (window as any).currentPageId,
|
parentId: (window as any).currentPageId,
|
||||||
type: ShapeType.Rectangle,
|
type: ShapeType.Rectangle,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { v4 as uuid } from 'uuid'
|
import { uniqueId } from 'utils/utils'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
import {
|
import {
|
||||||
ease,
|
ease,
|
||||||
getSvgPathFromStroke,
|
getSvgPathFromStroke,
|
||||||
|
@ -65,7 +65,7 @@ const arrow = registerShapeUtils<ArrowShape>({
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: uuid(),
|
id: uniqueId(),
|
||||||
seed: Math.random(),
|
seed: Math.random(),
|
||||||
type: ShapeType.Arrow,
|
type: ShapeType.Arrow,
|
||||||
isGenerated: false,
|
isGenerated: false,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { v4 as uuid } from 'uuid'
|
import { uniqueId } from 'utils/utils'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
import { CircleShape, ColorStyle, DashStyle, ShapeType, SizeStyle } from 'types'
|
import { CircleShape, ColorStyle, DashStyle, ShapeType, SizeStyle } from 'types'
|
||||||
import { registerShapeUtils } from './index'
|
import { registerShapeUtils } from './index'
|
||||||
import { boundsContained } from 'utils/bounds'
|
import { boundsContained } from 'utils/bounds'
|
||||||
|
@ -13,7 +13,7 @@ const circle = registerShapeUtils<CircleShape>({
|
||||||
|
|
||||||
create(props) {
|
create(props) {
|
||||||
return {
|
return {
|
||||||
id: uuid(),
|
id: uniqueId(),
|
||||||
seed: Math.random(),
|
seed: Math.random(),
|
||||||
type: ShapeType.Circle,
|
type: ShapeType.Circle,
|
||||||
isGenerated: false,
|
isGenerated: false,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { v4 as uuid } from 'uuid'
|
import { uniqueId } from 'utils/utils'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
import { DotShape, ShapeType } from 'types'
|
import { DotShape, ShapeType } from 'types'
|
||||||
import { registerShapeUtils } from './index'
|
import { registerShapeUtils } from './index'
|
||||||
import { boundsContained } from 'utils/bounds'
|
import { boundsContained } from 'utils/bounds'
|
||||||
|
@ -12,7 +12,7 @@ const dot = registerShapeUtils<DotShape>({
|
||||||
|
|
||||||
create(props) {
|
create(props) {
|
||||||
return {
|
return {
|
||||||
id: uuid(),
|
id: uniqueId(),
|
||||||
seed: Math.random(),
|
seed: Math.random(),
|
||||||
type: ShapeType.Dot,
|
type: ShapeType.Dot,
|
||||||
isGenerated: false,
|
isGenerated: false,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { v4 as uuid } from 'uuid'
|
import { uniqueId } from 'utils/utils'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
import { DashStyle, DrawShape, ShapeStyles, ShapeType } from 'types'
|
import { DashStyle, DrawShape, ShapeStyles, ShapeType } from 'types'
|
||||||
import { registerShapeUtils } from './index'
|
import { registerShapeUtils } from './index'
|
||||||
import { intersectPolylineBounds } from 'utils/intersections'
|
import { intersectPolylineBounds } from 'utils/intersections'
|
||||||
|
@ -23,7 +23,7 @@ const draw = registerShapeUtils<DrawShape>({
|
||||||
|
|
||||||
create(props) {
|
create(props) {
|
||||||
return {
|
return {
|
||||||
id: uuid(),
|
id: uniqueId(),
|
||||||
seed: Math.random(),
|
seed: Math.random(),
|
||||||
type: ShapeType.Draw,
|
type: ShapeType.Draw,
|
||||||
isGenerated: false,
|
isGenerated: false,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { v4 as uuid } from 'uuid'
|
import { uniqueId } from 'utils/utils'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
import { EllipseShape, ShapeType } from 'types'
|
import { EllipseShape, ShapeType } from 'types'
|
||||||
import { getShapeUtils, registerShapeUtils } from './index'
|
import { getShapeUtils, registerShapeUtils } from './index'
|
||||||
import { boundsContained, getRotatedEllipseBounds } from 'utils/bounds'
|
import { boundsContained, getRotatedEllipseBounds } from 'utils/bounds'
|
||||||
|
@ -16,7 +16,7 @@ const ellipse = registerShapeUtils<EllipseShape>({
|
||||||
|
|
||||||
create(props) {
|
create(props) {
|
||||||
return {
|
return {
|
||||||
id: uuid(),
|
id: uniqueId(),
|
||||||
seed: Math.random(),
|
seed: Math.random(),
|
||||||
type: ShapeType.Ellipse,
|
type: ShapeType.Ellipse,
|
||||||
isGenerated: false,
|
isGenerated: false,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { v4 as uuid } from 'uuid'
|
import { uniqueId } from 'utils/utils'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
import {
|
import {
|
||||||
GroupShape,
|
GroupShape,
|
||||||
RectangleShape,
|
RectangleShape,
|
||||||
|
@ -27,7 +27,7 @@ const group = registerShapeUtils<GroupShape>({
|
||||||
|
|
||||||
create(props) {
|
create(props) {
|
||||||
return {
|
return {
|
||||||
id: uuid(),
|
id: uniqueId(),
|
||||||
seed: Math.random(),
|
seed: Math.random(),
|
||||||
type: ShapeType.Group,
|
type: ShapeType.Group,
|
||||||
isGenerated: false,
|
isGenerated: false,
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
Mutable,
|
Mutable,
|
||||||
ShapeByType,
|
ShapeByType,
|
||||||
} from 'types'
|
} from 'types'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
import {
|
import {
|
||||||
getBoundsCenter,
|
getBoundsCenter,
|
||||||
getBoundsFromPoints,
|
getBoundsFromPoints,
|
||||||
|
@ -20,7 +20,7 @@ import {
|
||||||
boundsContainPolygon,
|
boundsContainPolygon,
|
||||||
pointInBounds,
|
pointInBounds,
|
||||||
} from 'utils/bounds'
|
} from 'utils/bounds'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { uniqueId } from 'utils/utils'
|
||||||
import circle from './circle'
|
import circle from './circle'
|
||||||
import dot from './dot'
|
import dot from './dot'
|
||||||
import polyline from './polyline'
|
import polyline from './polyline'
|
||||||
|
@ -231,7 +231,7 @@ function getDefaultShapeUtil<T extends Shape>(): ShapeUtility<T> {
|
||||||
|
|
||||||
create(props) {
|
create(props) {
|
||||||
return {
|
return {
|
||||||
id: uuid(),
|
id: uniqueId(),
|
||||||
isGenerated: false,
|
isGenerated: false,
|
||||||
point: [0, 0],
|
point: [0, 0],
|
||||||
name: 'Shape',
|
name: 'Shape',
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { v4 as uuid } from 'uuid'
|
import { uniqueId } from 'utils/utils'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
import { LineShape, ShapeType } from 'types'
|
import { LineShape, ShapeType } from 'types'
|
||||||
import { registerShapeUtils } from './index'
|
import { registerShapeUtils } from './index'
|
||||||
import { boundsContained } from 'utils/bounds'
|
import { boundsContained } from 'utils/bounds'
|
||||||
|
@ -14,7 +14,7 @@ const line = registerShapeUtils<LineShape>({
|
||||||
|
|
||||||
create(props) {
|
create(props) {
|
||||||
return {
|
return {
|
||||||
id: uuid(),
|
id: uniqueId(),
|
||||||
seed: Math.random(),
|
seed: Math.random(),
|
||||||
type: ShapeType.Line,
|
type: ShapeType.Line,
|
||||||
isGenerated: false,
|
isGenerated: false,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { v4 as uuid } from 'uuid'
|
import { uniqueId } from 'utils/utils'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
import { PolylineShape, ShapeType } from 'types'
|
import { PolylineShape, ShapeType } from 'types'
|
||||||
import { registerShapeUtils } from './index'
|
import { registerShapeUtils } from './index'
|
||||||
import { intersectPolylineBounds } from 'utils/intersections'
|
import { intersectPolylineBounds } from 'utils/intersections'
|
||||||
|
@ -12,7 +12,7 @@ const polyline = registerShapeUtils<PolylineShape>({
|
||||||
|
|
||||||
create(props) {
|
create(props) {
|
||||||
return {
|
return {
|
||||||
id: uuid(),
|
id: uniqueId(),
|
||||||
seed: Math.random(),
|
seed: Math.random(),
|
||||||
type: ShapeType.Polyline,
|
type: ShapeType.Polyline,
|
||||||
isGenerated: false,
|
isGenerated: false,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { v4 as uuid } from 'uuid'
|
import { uniqueId } from 'utils/utils'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
import { RayShape, ShapeType } from 'types'
|
import { RayShape, ShapeType } from 'types'
|
||||||
import { registerShapeUtils } from './index'
|
import { registerShapeUtils } from './index'
|
||||||
import { boundsContained } from 'utils/bounds'
|
import { boundsContained } from 'utils/bounds'
|
||||||
|
@ -13,7 +13,7 @@ const ray = registerShapeUtils<RayShape>({
|
||||||
|
|
||||||
create(props) {
|
create(props) {
|
||||||
return {
|
return {
|
||||||
id: uuid(),
|
id: uniqueId(),
|
||||||
seed: Math.random(),
|
seed: Math.random(),
|
||||||
type: ShapeType.Ray,
|
type: ShapeType.Ray,
|
||||||
isGenerated: false,
|
isGenerated: false,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { v4 as uuid } from 'uuid'
|
import { uniqueId } from 'utils/utils'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
import { RectangleShape, ShapeType } from 'types'
|
import { RectangleShape, ShapeType } from 'types'
|
||||||
import { registerShapeUtils } from './index'
|
import { registerShapeUtils } from './index'
|
||||||
import {
|
import {
|
||||||
|
@ -19,7 +19,7 @@ const rectangle = registerShapeUtils<RectangleShape>({
|
||||||
|
|
||||||
create(props) {
|
create(props) {
|
||||||
return {
|
return {
|
||||||
id: uuid(),
|
id: uniqueId(),
|
||||||
seed: Math.random(),
|
seed: Math.random(),
|
||||||
type: ShapeType.Rectangle,
|
type: ShapeType.Rectangle,
|
||||||
isGenerated: false,
|
isGenerated: false,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { v4 as uuid } from 'uuid'
|
import { uniqueId } from 'utils/utils'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
import { TextShape, ShapeType, FontSize } from 'types'
|
import { TextShape, ShapeType, FontSize } from 'types'
|
||||||
import { registerShapeUtils } from './index'
|
import { registerShapeUtils } from './index'
|
||||||
import { defaultStyle, getFontStyle, getShapeStyle } from 'lib/shape-styles'
|
import { defaultStyle, getFontStyle, getShapeStyle } from 'lib/shape-styles'
|
||||||
|
@ -36,7 +36,7 @@ const text = registerShapeUtils<TextShape>({
|
||||||
|
|
||||||
create(props) {
|
create(props) {
|
||||||
return {
|
return {
|
||||||
id: uuid(),
|
id: uniqueId(),
|
||||||
seed: Math.random(),
|
seed: Math.random(),
|
||||||
type: ShapeType.Text,
|
type: ShapeType.Text,
|
||||||
isGenerated: false,
|
isGenerated: false,
|
||||||
|
|
16
package.json
16
package.json
|
@ -9,22 +9,22 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@monaco-editor/react": "^4.1.3",
|
"@monaco-editor/react": "^4.1.3",
|
||||||
"@radix-ui/react-checkbox": "^0.0.15",
|
"@radix-ui/react-checkbox": "^0.0.16",
|
||||||
"@radix-ui/react-context-menu": "^0.0.19",
|
"@radix-ui/react-context-menu": "^0.0.21",
|
||||||
"@radix-ui/react-dialog": "^0.0.17",
|
"@radix-ui/react-dialog": "^0.0.18",
|
||||||
"@radix-ui/react-dropdown-menu": "^0.0.19",
|
"@radix-ui/react-dropdown-menu": "^0.0.20",
|
||||||
"@radix-ui/react-icons": "^1.0.3",
|
"@radix-ui/react-icons": "^1.0.3",
|
||||||
"@radix-ui/react-radio-group": "^0.0.16",
|
"@radix-ui/react-radio-group": "^0.0.17",
|
||||||
"@radix-ui/react-tooltip": "^0.0.18",
|
"@radix-ui/react-tooltip": "^0.0.19",
|
||||||
"@state-designer/react": "^1.7.3",
|
"@state-designer/react": "^1.7.3",
|
||||||
"@stitches/react": "^0.1.9",
|
"@stitches/react": "^0.2.1",
|
||||||
"browser-fs-access": "^0.17.3",
|
"browser-fs-access": "^0.17.3",
|
||||||
"framer-motion": "^4.1.16",
|
"framer-motion": "^4.1.16",
|
||||||
"idb-keyval": "^5.0.6",
|
"idb-keyval": "^5.0.6",
|
||||||
"ismobilejs": "^1.1.1",
|
"ismobilejs": "^1.1.1",
|
||||||
"next": "10.2.0",
|
"next": "10.2.0",
|
||||||
"next-pwa": "^5.2.21",
|
"next-pwa": "^5.2.21",
|
||||||
"perfect-freehand": "^0.4.8",
|
"perfect-freehand": "^0.4.9",
|
||||||
"prettier": "^2.3.0",
|
"prettier": "^2.3.0",
|
||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
"react-dom": "17.0.2",
|
"react-dom": "17.0.2",
|
||||||
|
|
|
@ -3,8 +3,8 @@ import history from '../history'
|
||||||
import { Data } from 'types'
|
import { Data } from 'types'
|
||||||
import storage from 'state/storage'
|
import storage from 'state/storage'
|
||||||
|
|
||||||
export default function changePage(data: Data, pageId: string) {
|
export default function changePage(data: Data, toPageId: string) {
|
||||||
const { currentPageId: prevPageId } = data
|
const { currentPageId: fromPageId } = data
|
||||||
|
|
||||||
history.execute(
|
history.execute(
|
||||||
data,
|
data,
|
||||||
|
@ -13,13 +13,13 @@ export default function changePage(data: Data, pageId: string) {
|
||||||
category: 'canvas',
|
category: 'canvas',
|
||||||
manualSelection: true,
|
manualSelection: true,
|
||||||
do(data) {
|
do(data) {
|
||||||
storage.savePage(data, data.document.id, prevPageId)
|
storage.savePage(data, data.document.id, fromPageId)
|
||||||
data.currentPageId = pageId
|
storage.loadPage(data, data.document.id, toPageId)
|
||||||
storage.loadPage(data)
|
data.currentPageId = toPageId
|
||||||
},
|
},
|
||||||
undo(data) {
|
undo(data) {
|
||||||
data.currentPageId = prevPageId
|
storage.loadPage(data, data.document.id, fromPageId)
|
||||||
storage.loadPage(data, prevPageId)
|
data.currentPageId = fromPageId
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import Command from './command'
|
import Command from './command'
|
||||||
import history from '../history'
|
import history from '../history'
|
||||||
import { Data, Page, PageState } from 'types'
|
import { Data, Page, PageState } from 'types'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { uniqueId } from 'utils/utils'
|
||||||
import { current } from 'immer'
|
import { current } from 'immer'
|
||||||
import { getSelectedIds } from 'utils/utils'
|
|
||||||
import storage from 'state/storage'
|
import storage from 'state/storage'
|
||||||
|
|
||||||
export default function createPage(data: Data) {
|
export default function createPage(data: Data) {
|
||||||
|
@ -20,12 +19,14 @@ export default function createPage(data: Data) {
|
||||||
data.pageStates[page.id] = pageState
|
data.pageStates[page.id] = pageState
|
||||||
data.currentPageId = page.id
|
data.currentPageId = page.id
|
||||||
storage.savePage(data, data.document.id, page.id)
|
storage.savePage(data, data.document.id, page.id)
|
||||||
|
storage.saveDocumentToLocalStorage(data)
|
||||||
},
|
},
|
||||||
undo(data) {
|
undo(data) {
|
||||||
const { page, currentPageId } = snapshot
|
const { page, currentPageId } = snapshot
|
||||||
delete data.document.pages[page.id]
|
delete data.document.pages[page.id]
|
||||||
delete data.pageStates[page.id]
|
delete data.pageStates[page.id]
|
||||||
data.currentPageId = currentPageId
|
data.currentPageId = currentPageId
|
||||||
|
storage.saveDocumentToLocalStorage(data)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -36,7 +37,7 @@ function getSnapshot(data: Data) {
|
||||||
|
|
||||||
const pages = Object.values(data.document.pages)
|
const pages = Object.values(data.document.pages)
|
||||||
const unchanged = pages.filter((page) => page.name.startsWith('Page '))
|
const unchanged = pages.filter((page) => page.name.startsWith('Page '))
|
||||||
const id = uuid()
|
const id = uniqueId()
|
||||||
|
|
||||||
const page: Page = {
|
const page: Page = {
|
||||||
type: 'page',
|
type: 'page',
|
||||||
|
@ -46,7 +47,8 @@ function getSnapshot(data: Data) {
|
||||||
shapes: {},
|
shapes: {},
|
||||||
}
|
}
|
||||||
const pageState: PageState = {
|
const pageState: PageState = {
|
||||||
selectedIds: new Set<string>(),
|
id,
|
||||||
|
selectedIds: new Set([]),
|
||||||
camera: {
|
camera: {
|
||||||
point: [0, 0],
|
point: [0, 0],
|
||||||
zoom: 1,
|
zoom: 1,
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { Data } from 'types'
|
||||||
import { current } from 'immer'
|
import { current } from 'immer'
|
||||||
import { getPage, getSelectedShapes } from 'utils/utils'
|
import { getPage, getSelectedShapes } from 'utils/utils'
|
||||||
import { getShapeUtils } from 'lib/shape-utils'
|
import { getShapeUtils } from 'lib/shape-utils'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
import storage from 'state/storage'
|
import storage from 'state/storage'
|
||||||
|
|
||||||
export default function changePage(data: Data, pageId: string) {
|
export default function changePage(data: Data, pageId: string) {
|
||||||
|
|
|
@ -8,16 +8,16 @@ import {
|
||||||
getSelectedShapes,
|
getSelectedShapes,
|
||||||
setSelectedIds,
|
setSelectedIds,
|
||||||
} from 'utils/utils'
|
} from 'utils/utils'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { uniqueId } from 'utils/utils'
|
||||||
import { current } from 'immer'
|
import { current } from 'immer'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
|
|
||||||
export default function duplicateCommand(data: Data) {
|
export default function duplicateCommand(data: Data) {
|
||||||
const { currentPageId } = data
|
const { currentPageId } = data
|
||||||
const selectedShapes = getSelectedShapes(current(data))
|
const selectedShapes = getSelectedShapes(current(data))
|
||||||
const duplicates = selectedShapes.map((shape) => ({
|
const duplicates = selectedShapes.map((shape) => ({
|
||||||
...shape,
|
...shape,
|
||||||
id: uuid(),
|
id: uniqueId(),
|
||||||
point: vec.add(shape.point, vec.div([16, 16], getCurrentCamera(data).zoom)),
|
point: vec.add(shape.point, vec.div([16, 16], getCurrentCamera(data).zoom)),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {
|
||||||
import { current } from 'immer'
|
import { current } from 'immer'
|
||||||
import { createShape, getShapeUtils } from 'lib/shape-utils'
|
import { createShape, getShapeUtils } from 'lib/shape-utils'
|
||||||
import { PropsOfType } from 'types'
|
import { PropsOfType } from 'types'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { uniqueId } from 'utils/utils'
|
||||||
import commands from '.'
|
import commands from '.'
|
||||||
|
|
||||||
export default function groupCommand(data: Data) {
|
export default function groupCommand(data: Data) {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { Data } from 'types'
|
||||||
import { getPage } from 'utils/utils'
|
import { getPage } from 'utils/utils'
|
||||||
import { HandleSnapshot } from 'state/sessions/handle-session'
|
import { HandleSnapshot } from 'state/sessions/handle-session'
|
||||||
import { getShapeUtils } from 'lib/shape-utils'
|
import { getShapeUtils } from 'lib/shape-utils'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
|
|
||||||
export default function handleCommand(
|
export default function handleCommand(
|
||||||
data: Data,
|
data: Data,
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {
|
||||||
uniqueArray,
|
uniqueArray,
|
||||||
} from 'utils/utils'
|
} from 'utils/utils'
|
||||||
import { getShapeUtils } from 'lib/shape-utils'
|
import { getShapeUtils } from 'lib/shape-utils'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
import storage from 'state/storage'
|
import storage from 'state/storage'
|
||||||
|
|
||||||
export default function nudgeCommand(data: Data, newPageId: string) {
|
export default function nudgeCommand(data: Data, newPageId: string) {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import history from '../history'
|
||||||
import { Data } from 'types'
|
import { Data } from 'types'
|
||||||
import { getPage, getSelectedShapes } from 'utils/utils'
|
import { getPage, getSelectedShapes } from 'utils/utils'
|
||||||
import { getShapeUtils } from 'lib/shape-utils'
|
import { getShapeUtils } from 'lib/shape-utils'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
|
|
||||||
export default function nudgeCommand(data: Data, delta: number[]) {
|
export default function nudgeCommand(data: Data, delta: number[]) {
|
||||||
const { currentPageId } = data
|
const { currentPageId } = data
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
getPage,
|
getPage,
|
||||||
getSelectedShapes,
|
getSelectedShapes,
|
||||||
} from 'utils/utils'
|
} from 'utils/utils'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
import { getShapeUtils } from 'lib/shape-utils'
|
import { getShapeUtils } from 'lib/shape-utils'
|
||||||
|
|
||||||
const PI2 = Math.PI * 2
|
const PI2 = Math.PI * 2
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
updateParents,
|
updateParents,
|
||||||
} from 'utils/utils'
|
} from 'utils/utils'
|
||||||
import { getShapeUtils } from 'lib/shape-utils'
|
import { getShapeUtils } from 'lib/shape-utils'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { uniqueId } from 'utils/utils'
|
||||||
|
|
||||||
export default function translateCommand(
|
export default function translateCommand(
|
||||||
data: Data,
|
data: Data,
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {
|
||||||
import { current } from 'immer'
|
import { current } from 'immer'
|
||||||
import { createShape, getShapeUtils } from 'lib/shape-utils'
|
import { createShape, getShapeUtils } from 'lib/shape-utils'
|
||||||
import { PropsOfType } from 'types'
|
import { PropsOfType } from 'types'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { uniqueId } from 'utils/utils'
|
||||||
|
|
||||||
export default function ungroupCommand(data: Data) {
|
export default function ungroupCommand(data: Data) {
|
||||||
const cData = current(data)
|
const cData = current(data)
|
||||||
|
|
|
@ -7,9 +7,10 @@ import {
|
||||||
setToArray,
|
setToArray,
|
||||||
setZoomCSS,
|
setZoomCSS,
|
||||||
} from 'utils/utils'
|
} from 'utils/utils'
|
||||||
|
import { freeze } from 'immer'
|
||||||
import session from './session'
|
import session from './session'
|
||||||
import state from './state'
|
import state from './state'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* While a user is drawing with the draw tool, we want to update the shape without
|
* While a user is drawing with the draw tool, we want to update the shape without
|
||||||
|
@ -34,7 +35,7 @@ export function fastDrawUpdate(info: PointerInfo) {
|
||||||
|
|
||||||
data.document.pages[data.currentPageId].shapes[selectedId] = { ...shape }
|
data.document.pages[data.currentPageId].shapes[selectedId] = { ...shape }
|
||||||
|
|
||||||
state.forceData(Object.freeze(data))
|
state.forceData(freeze(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fastPanUpdate(delta: number[]) {
|
export function fastPanUpdate(delta: number[]) {
|
||||||
|
@ -44,7 +45,7 @@ export function fastPanUpdate(delta: number[]) {
|
||||||
|
|
||||||
data.pageStates[data.currentPageId].camera = { ...camera }
|
data.pageStates[data.currentPageId].camera = { ...camera }
|
||||||
|
|
||||||
state.forceData(Object.freeze(data))
|
state.forceData(freeze(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fastZoomUpdate(point: number[], delta: number) {
|
export function fastZoomUpdate(point: number[], delta: number) {
|
||||||
|
@ -60,7 +61,7 @@ export function fastZoomUpdate(point: number[], delta: number) {
|
||||||
|
|
||||||
data.pageStates[data.currentPageId].camera = { ...camera }
|
data.pageStates[data.currentPageId].camera = { ...camera }
|
||||||
|
|
||||||
state.forceData(Object.freeze(data))
|
state.forceData(freeze(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fastPinchCamera(
|
export function fastPinchCamera(
|
||||||
|
@ -86,7 +87,7 @@ export function fastPinchCamera(
|
||||||
|
|
||||||
data.pageStates[data.currentPageId] = { ...pageState }
|
data.pageStates[data.currentPageId] = { ...pageState }
|
||||||
|
|
||||||
state.forceData(Object.freeze(data))
|
state.forceData(freeze(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fastBrushSelect(point: number[]) {
|
export function fastBrushSelect(point: number[]) {
|
||||||
|
@ -94,7 +95,7 @@ export function fastBrushSelect(point: number[]) {
|
||||||
|
|
||||||
session.current.update(data, screenToWorld(point, data))
|
session.current.update(data, screenToWorld(point, data))
|
||||||
|
|
||||||
state.forceData(Object.freeze(data))
|
state.forceData(freeze(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fastTranslate(info: PointerInfo) {
|
export function fastTranslate(info: PointerInfo) {
|
||||||
|
@ -107,5 +108,18 @@ export function fastTranslate(info: PointerInfo) {
|
||||||
info.altKey
|
info.altKey
|
||||||
)
|
)
|
||||||
|
|
||||||
state.forceData(Object.freeze(data))
|
state.forceData(freeze(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fastTransform(info: PointerInfo) {
|
||||||
|
const data = { ...state.data }
|
||||||
|
|
||||||
|
session.current.update(
|
||||||
|
data,
|
||||||
|
screenToWorld(info.point, data),
|
||||||
|
info.shiftKey,
|
||||||
|
info.altKey
|
||||||
|
)
|
||||||
|
|
||||||
|
state.forceData(freeze(data))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Data, Page, PageState } from 'types'
|
import { Data } from 'types'
|
||||||
import { BaseCommand } from './commands/command'
|
import { BaseCommand } from './commands/command'
|
||||||
import storage from './storage'
|
import storage from './storage'
|
||||||
|
|
||||||
|
@ -24,7 +24,6 @@ class History<T extends Data> {
|
||||||
}
|
}
|
||||||
|
|
||||||
storage.savePage(data)
|
storage.savePage(data)
|
||||||
// storage.saveToLocalStorage(data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
undo = (data: T) => {
|
undo = (data: T) => {
|
||||||
|
@ -34,7 +33,6 @@ class History<T extends Data> {
|
||||||
if (this.disabled) return
|
if (this.disabled) return
|
||||||
this.pointer--
|
this.pointer--
|
||||||
storage.savePage(data)
|
storage.savePage(data)
|
||||||
// storage.saveToLocalStorage(data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
redo = (data: T) => {
|
redo = (data: T) => {
|
||||||
|
@ -44,7 +42,6 @@ class History<T extends Data> {
|
||||||
if (this.disabled) return
|
if (this.disabled) return
|
||||||
this.pointer++
|
this.pointer++
|
||||||
storage.savePage(data)
|
storage.savePage(data)
|
||||||
// storage.saveToLocalStorage(data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
disable = () => {
|
disable = () => {
|
||||||
|
@ -62,6 +59,13 @@ class History<T extends Data> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.stack = []
|
||||||
|
this.pointer = -1
|
||||||
|
this.maxLength = 100
|
||||||
|
this._enabled = true
|
||||||
|
}
|
||||||
|
|
||||||
get disabled() {
|
get disabled() {
|
||||||
return !this._enabled
|
return !this._enabled
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { PointerInfo } from 'types'
|
import { PointerInfo } from 'types'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
import { isDarwin, getPoint } from 'utils/utils'
|
import { isDarwin, getPoint } from 'utils/utils'
|
||||||
|
|
||||||
const DOUBLE_CLICK_DURATION = 300
|
const DOUBLE_CLICK_DURATION = 300
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { ArrowShape, Data, LineShape, RayShape } from 'types'
|
import { ArrowShape, Data, LineShape, RayShape } from 'types'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
import BaseSession from './base-session'
|
import BaseSession from './base-session'
|
||||||
import commands from 'state/commands'
|
import commands from 'state/commands'
|
||||||
import { current } from 'immer'
|
import { current } from 'immer'
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {
|
||||||
setSelectedIds,
|
setSelectedIds,
|
||||||
setToArray,
|
setToArray,
|
||||||
} from 'utils/utils'
|
} from 'utils/utils'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
import state from 'state/state'
|
import state from 'state/state'
|
||||||
|
|
||||||
export default class BrushSession extends BaseSession {
|
export default class BrushSession extends BaseSession {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Data, LineShape, RayShape } from 'types'
|
import { Data, LineShape, RayShape } from 'types'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
import BaseSession from './base-session'
|
import BaseSession from './base-session'
|
||||||
import commands from 'state/commands'
|
import commands from 'state/commands'
|
||||||
import { current } from 'immer'
|
import { current } from 'immer'
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { Data, DrawShape } from 'types'
|
||||||
import BaseSession from './base-session'
|
import BaseSession from './base-session'
|
||||||
import { getShapeUtils } from 'lib/shape-utils'
|
import { getShapeUtils } from 'lib/shape-utils'
|
||||||
import { getPage, getShape, isMobile, updateParents } from 'utils/utils'
|
import { getPage, getShape, isMobile, updateParents } from 'utils/utils'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
import commands from 'state/commands'
|
import commands from 'state/commands'
|
||||||
export default class BrushSession extends BaseSession {
|
export default class BrushSession extends BaseSession {
|
||||||
origin: number[]
|
origin: number[]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Data, LineShape, RayShape, Shape } from 'types'
|
import { Data, LineShape, RayShape, Shape } from 'types'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
import BaseSession from './base-session'
|
import BaseSession from './base-session'
|
||||||
import commands from 'state/commands'
|
import commands from 'state/commands'
|
||||||
import { current } from 'immer'
|
import { current } from 'immer'
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Data } from 'types'
|
import { Data } from 'types'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
import BaseSession from './base-session'
|
import BaseSession from './base-session'
|
||||||
import commands from 'state/commands'
|
import commands from 'state/commands'
|
||||||
import { current } from 'immer'
|
import { current } from 'immer'
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Data, ShapeType } from 'types'
|
import { Data, ShapeType } from 'types'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
import BaseSession from './base-session'
|
import BaseSession from './base-session'
|
||||||
import commands from 'state/commands'
|
import commands from 'state/commands'
|
||||||
import { current } from 'immer'
|
import { current } from 'immer'
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { Data, Edge, Corner, Bounds } from 'types'
|
import { Data, Edge, Corner, Bounds } from 'types'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
import BaseSession from './base-session'
|
import BaseSession from './base-session'
|
||||||
import commands from 'state/commands'
|
import commands from 'state/commands'
|
||||||
import { current } from 'immer'
|
import { current, freeze } from 'immer'
|
||||||
import { getShapeUtils } from 'lib/shape-utils'
|
import { getShapeUtils } from 'lib/shape-utils'
|
||||||
import {
|
import {
|
||||||
getBoundsCenter,
|
getBoundsCenter,
|
||||||
|
@ -72,6 +72,8 @@ export default class TransformSession extends BaseSession {
|
||||||
scaleY: this.scaleY,
|
scaleY: this.scaleY,
|
||||||
transformOrigin,
|
transformOrigin,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
shapes[id] = { ...shape }
|
||||||
}
|
}
|
||||||
|
|
||||||
updateParents(data, Object.keys(shapeBounds))
|
updateParents(data, Object.keys(shapeBounds))
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
import { Data, Edge, Corner } from 'types'
|
import { Data, Edge, Corner } from 'types'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
import BaseSession from './base-session'
|
import BaseSession from './base-session'
|
||||||
import commands from 'state/commands'
|
import commands from 'state/commands'
|
||||||
import { current } from 'immer'
|
import { current } from 'immer'
|
||||||
import { getShapeUtils } from 'lib/shape-utils'
|
import { getShapeUtils } from 'lib/shape-utils'
|
||||||
import {
|
import {
|
||||||
getTransformedBoundingBox,
|
getTransformedBoundingBox,
|
||||||
getCommonBounds,
|
|
||||||
getRotatedCorners,
|
|
||||||
getTransformAnchor,
|
|
||||||
getPage,
|
getPage,
|
||||||
getShape,
|
getShape,
|
||||||
getSelectedShapes,
|
getSelectedShapes,
|
||||||
|
@ -63,6 +60,8 @@ export default class TransformSingleSession extends BaseSession {
|
||||||
transformOrigin: [0.5, 0.5],
|
transformOrigin: [0.5, 0.5],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
data.document.pages[data.currentPageId].shapes[shape.id] = { ...shape }
|
||||||
|
|
||||||
updateParents(data, [id])
|
updateParents(data, [id])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { Data, GroupShape, Shape, ShapeType } from 'types'
|
import { Data, GroupShape, Shape, ShapeType } from 'types'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
import BaseSession from './base-session'
|
import BaseSession from './base-session'
|
||||||
import commands from 'state/commands'
|
import commands from 'state/commands'
|
||||||
import { current } from 'immer'
|
import { current } from 'immer'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { uniqueId } from 'utils/utils'
|
||||||
import {
|
import {
|
||||||
getChildIndexAbove,
|
getChildIndexAbove,
|
||||||
getDocumentBranch,
|
getDocumentBranch,
|
||||||
|
@ -215,7 +215,7 @@ export function getTranslateSnapshot(data: Data) {
|
||||||
.flatMap((shape) => {
|
.flatMap((shape) => {
|
||||||
const clone = {
|
const clone = {
|
||||||
...shape,
|
...shape,
|
||||||
id: uuid(),
|
id: uniqueId(),
|
||||||
seed: Math.random(),
|
seed: Math.random(),
|
||||||
parentId: shape.parentId,
|
parentId: shape.parentId,
|
||||||
childIndex: getChildIndexAbove(cData, shape.id),
|
childIndex: getChildIndexAbove(cData, shape.id),
|
||||||
|
@ -235,7 +235,7 @@ function cloneGroup(data: Data, clone: Shape): Shape[] {
|
||||||
|
|
||||||
const page = getPage(data)
|
const page = getPage(data)
|
||||||
const childClones = clone.children.flatMap((id) => {
|
const childClones = clone.children.flatMap((id) => {
|
||||||
const newId = uuid()
|
const newId = uniqueId()
|
||||||
const source = page.shapes[id]
|
const source = page.shapes[id]
|
||||||
const next = { ...source, id: newId, parentId: clone.id }
|
const next = { ...source, id: newId, parentId: clone.id }
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import { createSelectorHook, createState } from '@state-designer/react'
|
import { createSelectorHook, createState } from '@state-designer/react'
|
||||||
import { updateFromCode } from 'lib/code/generate'
|
import { updateFromCode } from 'lib/code/generate'
|
||||||
import { createShape, getShapeUtils } from 'lib/shape-utils'
|
import { createShape, getShapeUtils } from 'lib/shape-utils'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
import inputs from './inputs'
|
import inputs from './inputs'
|
||||||
import { defaultDocument } from './data'
|
|
||||||
import history from './history'
|
import history from './history'
|
||||||
import storage from './storage'
|
import storage from './storage'
|
||||||
import * as Sessions from './sessions'
|
import * as Sessions from './sessions'
|
||||||
|
@ -77,16 +76,29 @@ const initialData: Data = {
|
||||||
currentParentId: 'page1',
|
currentParentId: 'page1',
|
||||||
currentCodeFileId: 'file0',
|
currentCodeFileId: 'file0',
|
||||||
codeControls: {},
|
codeControls: {},
|
||||||
document: defaultDocument,
|
document: {
|
||||||
pageStates: {
|
id: '0001',
|
||||||
page1: {
|
name: 'My Document',
|
||||||
selectedIds: new Set([]),
|
pages: {
|
||||||
camera: {
|
page1: {
|
||||||
point: [0, 0],
|
id: 'page1',
|
||||||
zoom: 1,
|
type: 'page',
|
||||||
|
name: 'Page 1',
|
||||||
|
childIndex: 0,
|
||||||
|
shapes: {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
page2: {
|
code: {
|
||||||
|
file0: {
|
||||||
|
id: 'file0',
|
||||||
|
name: 'index.ts',
|
||||||
|
code: ``,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pageStates: {
|
||||||
|
page1: {
|
||||||
|
id: 'page1',
|
||||||
selectedIds: new Set([]),
|
selectedIds: new Set([]),
|
||||||
camera: {
|
camera: {
|
||||||
point: [0, 0],
|
point: [0, 0],
|
||||||
|
@ -141,7 +153,7 @@ const state = createState({
|
||||||
CHANGED_PAGE: 'changePage',
|
CHANGED_PAGE: 'changePage',
|
||||||
CREATED_PAGE: ['clearSelectedIds', 'createPage'],
|
CREATED_PAGE: ['clearSelectedIds', 'createPage'],
|
||||||
DELETED_PAGE: { unless: 'hasOnlyOnePage', do: 'deletePage' },
|
DELETED_PAGE: { unless: 'hasOnlyOnePage', do: 'deletePage' },
|
||||||
LOADED_FROM_FILE: 'loadDocumentFromJson',
|
LOADED_FROM_FILE: ['loadDocumentFromJson', 'resetHistory'],
|
||||||
PANNED_CAMERA: {
|
PANNED_CAMERA: {
|
||||||
do: 'panCamera',
|
do: 'panCamera',
|
||||||
},
|
},
|
||||||
|
@ -362,7 +374,7 @@ const state = createState({
|
||||||
transformingSelection: {
|
transformingSelection: {
|
||||||
onEnter: 'startTransformSession',
|
onEnter: 'startTransformSession',
|
||||||
on: {
|
on: {
|
||||||
MOVED_POINTER: 'updateTransformSession',
|
// MOVED_POINTER: 'updateTransformSession', using hacks.fastTransform
|
||||||
PANNED_CAMERA: 'updateTransformSession',
|
PANNED_CAMERA: 'updateTransformSession',
|
||||||
PRESSED_SHIFT_KEY: 'keyUpdateTransformSession',
|
PRESSED_SHIFT_KEY: 'keyUpdateTransformSession',
|
||||||
RELEASED_SHIFT_KEY: 'keyUpdateTransformSession',
|
RELEASED_SHIFT_KEY: 'keyUpdateTransformSession',
|
||||||
|
@ -405,8 +417,7 @@ const state = createState({
|
||||||
],
|
],
|
||||||
on: {
|
on: {
|
||||||
STARTED_PINCHING: { do: 'completeSession', to: 'pinching' },
|
STARTED_PINCHING: { do: 'completeSession', to: 'pinching' },
|
||||||
// Currently using hacks.fastBrushSelect
|
// MOVED_POINTER: 'updateBrushSession', using hacks.fastBrushSelect
|
||||||
// MOVED_POINTER: 'updateBrushSession',
|
|
||||||
PANNED_CAMERA: 'updateBrushSession',
|
PANNED_CAMERA: 'updateBrushSession',
|
||||||
STOPPED_POINTING: { do: 'completeSession', to: 'selecting' },
|
STOPPED_POINTING: { do: 'completeSession', to: 'selecting' },
|
||||||
CANCELLED: { do: 'cancelSession', to: 'selecting' },
|
CANCELLED: { do: 'cancelSession', to: 'selecting' },
|
||||||
|
@ -425,8 +436,7 @@ const state = createState({
|
||||||
},
|
},
|
||||||
pinching: {
|
pinching: {
|
||||||
on: {
|
on: {
|
||||||
// Pinching uses hacks.fastPinchCamera
|
// PINCHED: { do: 'pinchCamera' }, using hacks.fastPinchCamera
|
||||||
// PINCHED: { do: 'pinchCamera' },
|
|
||||||
},
|
},
|
||||||
initial: 'selectPinching',
|
initial: 'selectPinching',
|
||||||
onExit: { secretlyDo: 'updateZoomCSS' },
|
onExit: { secretlyDo: 'updateZoomCSS' },
|
||||||
|
@ -1498,6 +1508,9 @@ const state = createState({
|
||||||
redo(data) {
|
redo(data) {
|
||||||
history.redo(data)
|
history.redo(data)
|
||||||
},
|
},
|
||||||
|
resetHistory(data) {
|
||||||
|
history.reset()
|
||||||
|
},
|
||||||
|
|
||||||
/* --------------------- Styles --------------------- */
|
/* --------------------- Styles --------------------- */
|
||||||
|
|
||||||
|
@ -1595,8 +1608,8 @@ const state = createState({
|
||||||
storage.loadDocumentFromFilesystem()
|
storage.loadDocumentFromFilesystem()
|
||||||
},
|
},
|
||||||
|
|
||||||
loadDocumentFromJson(data, payload: { restoredData: any }) {
|
loadDocumentFromJson(data, payload: { json: any }) {
|
||||||
storage.loadDocumentFromJson(data, payload.restoredData)
|
storage.loadDocumentFromJson(data, payload.json)
|
||||||
},
|
},
|
||||||
|
|
||||||
forceSave(data) {
|
forceSave(data) {
|
||||||
|
@ -1613,7 +1626,7 @@ const state = createState({
|
||||||
|
|
||||||
saveCode(data, payload: { code: string }) {
|
saveCode(data, payload: { code: string }) {
|
||||||
data.document.code[data.currentCodeFileId].code = payload.code
|
data.document.code[data.currentCodeFileId].code = payload.code
|
||||||
storage.saveToLocalStorage(data)
|
storage.saveDocumentToLocalStorage(data)
|
||||||
},
|
},
|
||||||
|
|
||||||
clearBoundsRotation(data) {
|
clearBoundsRotation(data) {
|
||||||
|
|
332
state/storage.ts
332
state/storage.ts
|
@ -1,13 +1,11 @@
|
||||||
import * as fa from 'browser-fs-access'
|
import * as fa from 'browser-fs-access'
|
||||||
import { Data, Page, PageState, TLDocument } from 'types'
|
import { Data, PageState, TLDocument } from 'types'
|
||||||
import { decompress, compress, setToArray } from 'utils/utils'
|
import { decompress, compress, setToArray } from 'utils/utils'
|
||||||
import state from './state'
|
import state from './state'
|
||||||
import { current } from 'immer'
|
import { uniqueId } from 'utils/utils'
|
||||||
import { v4 as uuid } from 'uuid'
|
|
||||||
import * as idb from 'idb-keyval'
|
import * as idb from 'idb-keyval'
|
||||||
|
|
||||||
const CURRENT_VERSION = 'code_slate_0.0.7'
|
const CURRENT_VERSION = 'code_slate_0.0.7'
|
||||||
const DOCUMENT_ID = '0001'
|
|
||||||
|
|
||||||
function storageId(fileId: string, label: string, id?: string) {
|
function storageId(fileId: string, label: string, id?: string) {
|
||||||
return [CURRENT_VERSION, fileId, label, id].filter(Boolean).join('_')
|
return [CURRENT_VERSION, fileId, label, id].filter(Boolean).join('_')
|
||||||
|
@ -21,99 +19,186 @@ class Storage {
|
||||||
}
|
}
|
||||||
|
|
||||||
firstLoad(data: Data) {
|
firstLoad(data: Data) {
|
||||||
const lastOpened = localStorage.getItem(`${CURRENT_VERSION}_lastOpened`)
|
const lastOpenedFileId = localStorage.getItem(
|
||||||
|
`${CURRENT_VERSION}_lastOpened`
|
||||||
this.loadDocumentFromLocalStorage(data, lastOpened || DOCUMENT_ID)
|
|
||||||
|
|
||||||
this.loadPage(data, data.currentPageId)
|
|
||||||
|
|
||||||
this.saveToLocalStorage(data, data.document.id)
|
|
||||||
|
|
||||||
localStorage.setItem(`${CURRENT_VERSION}_lastOpened`, data.document.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
load(data: Data, restoredData: any) {
|
|
||||||
// Before loading the state, save the pages / page states
|
|
||||||
|
|
||||||
// Empty current state.
|
|
||||||
data.document = {} as TLDocument
|
|
||||||
data.pageStates = {}
|
|
||||||
|
|
||||||
// Merge restored data into state.
|
|
||||||
Object.assign(data, restoredData)
|
|
||||||
|
|
||||||
// Add id and name to document, just in case.
|
|
||||||
data.document = {
|
|
||||||
id: 'document0',
|
|
||||||
name: 'My Document',
|
|
||||||
...restoredData.document,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loadDocumentFromLocalStorage(data: Data, fileId = DOCUMENT_ID) {
|
|
||||||
if (typeof window === 'undefined') return
|
|
||||||
if (typeof localStorage === 'undefined') return
|
|
||||||
|
|
||||||
// Load data from local storage
|
|
||||||
const savedData = localStorage.getItem(
|
|
||||||
storageId(fileId, 'document', fileId)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if (savedData === null) {
|
// 1. Load Document from Local Storage
|
||||||
// If we're going to use the default data, assign the
|
// Using the "last opened file id" in local storage.
|
||||||
// current document a fresh random id.
|
if (lastOpenedFileId !== null) {
|
||||||
data.document.id = uuid()
|
// Load document from local storage
|
||||||
return false
|
const savedDocument = localStorage.getItem(
|
||||||
|
storageId(lastOpenedFileId, 'document', lastOpenedFileId)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (savedDocument === null) {
|
||||||
|
// If no document found, create a fresh random id.
|
||||||
|
data.document.id = uniqueId()
|
||||||
|
} else {
|
||||||
|
// If we did find a document, load it into state.
|
||||||
|
const restoredDocument: TLDocument = JSON.parse(
|
||||||
|
decompress(savedDocument)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Merge restored data into state.
|
||||||
|
data.document = restoredDocument
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const restoredData: any = JSON.parse(decompress(savedData))
|
this.load(data)
|
||||||
|
|
||||||
this.load(data, restoredData)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getDataToSave = (data: Data) => {
|
saveDocumentToLocalStorage(data: Data) {
|
||||||
const dataToSave = current(data) as any
|
const document = this.getCompleteDocument(data)
|
||||||
|
|
||||||
for (let pageId in data.document.pages) {
|
localStorage.setItem(
|
||||||
|
storageId(data.document.id, 'document', data.document.id),
|
||||||
|
compress(JSON.stringify(document))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
getCompleteDocument = (data: Data) => {
|
||||||
|
// Create a safely mutable copy of the data
|
||||||
|
const document: TLDocument = { ...data.document }
|
||||||
|
|
||||||
|
// Try to find the document's pages and page states in local storage.
|
||||||
|
Object.keys(document.pages).forEach((pageId) => {
|
||||||
|
const savedPage = localStorage.getItem(
|
||||||
|
storageId(document.id, 'page', pageId)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (savedPage !== null) {
|
||||||
|
document.pages[pageId] = JSON.parse(decompress(savedPage))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return document
|
||||||
|
}
|
||||||
|
|
||||||
|
savePageState = (data: Data) => {
|
||||||
|
localStorage.setItem(
|
||||||
|
storageId(data.document.id, 'lastPageState', data.document.id),
|
||||||
|
JSON.stringify(data.pageStates[data.currentPageId])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
loadDocumentFromJson(data: Data, json: string) {
|
||||||
|
const restoredDocument: { document: TLDocument; pageState: PageState } =
|
||||||
|
JSON.parse(json)
|
||||||
|
|
||||||
|
data.document = restoredDocument.document
|
||||||
|
|
||||||
|
// Save pages to local storage, possibly overwriting unsaved local copies
|
||||||
|
Object.values(data.document.pages).forEach((page) => {
|
||||||
|
localStorage.setItem(
|
||||||
|
storageId(data.document.id, 'page', page.id),
|
||||||
|
compress(JSON.stringify(page))
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
localStorage.setItem(
|
||||||
|
storageId(data.document.id, 'lastPageState', data.document.id),
|
||||||
|
JSON.stringify(restoredDocument.pageState)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Save the new file as the last opened document id
|
||||||
|
localStorage.setItem(`${CURRENT_VERSION}_lastOpened`, data.document.id)
|
||||||
|
|
||||||
|
this.load(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
load(data: Data) {
|
||||||
|
// Once we've loaded data either from local storage or json, run through these steps.
|
||||||
|
data.pageStates = {}
|
||||||
|
|
||||||
|
// 2. Load Pages from Local Storage
|
||||||
|
// Try to find the document's pages and page states in local storage.
|
||||||
|
Object.keys(data.document.pages).forEach((pageId) => {
|
||||||
const savedPage = localStorage.getItem(
|
const savedPage = localStorage.getItem(
|
||||||
storageId(data.document.id, 'page', pageId)
|
storageId(data.document.id, 'page', pageId)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (savedPage !== null) {
|
if (savedPage !== null) {
|
||||||
const restored: Page = JSON.parse(decompress(savedPage))
|
// If we've found a page in local storage, set it into state.
|
||||||
dataToSave.document.pages[pageId] = restored
|
data.document.pages[pageId] = JSON.parse(decompress(savedPage))
|
||||||
}
|
}
|
||||||
|
|
||||||
const pageState = { ...dataToSave.pageStates[pageId] }
|
const savedPageState = localStorage.getItem(
|
||||||
pageState.selectedIds = setToArray(pageState.selectedIds)
|
storageId(data.document.id, 'pageState', pageId)
|
||||||
}
|
)
|
||||||
|
|
||||||
return JSON.stringify(dataToSave, null, 2)
|
if (savedPageState !== null) {
|
||||||
}
|
// If we've found a page state in local storage, set it into state.
|
||||||
|
data.pageStates[pageId] = JSON.parse(decompress(savedPageState))
|
||||||
|
data.pageStates[pageId].selectedIds = new Set([])
|
||||||
|
} else {
|
||||||
|
// Or else create a new one.
|
||||||
|
data.pageStates[pageId] = {
|
||||||
|
id: pageId,
|
||||||
|
selectedIds: new Set([]),
|
||||||
|
camera: {
|
||||||
|
point: [0, 0],
|
||||||
|
zoom: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
saveToLocalStorage = (data: Data, fileId = data.document.id) => {
|
// 3. Restore the last page state
|
||||||
if (typeof window === 'undefined') return
|
// Using the "last page state" in local storage.
|
||||||
if (typeof localStorage === 'undefined') return
|
const savedPageState = localStorage.getItem(
|
||||||
|
storageId(data.document.id, 'lastPageState', data.document.id)
|
||||||
const dataToSave = this.getDataToSave(data)
|
|
||||||
|
|
||||||
// Save current data to local storage
|
|
||||||
localStorage.setItem(
|
|
||||||
storageId(fileId, 'document', fileId),
|
|
||||||
compress(dataToSave)
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
loadDocumentFromJson(data: Data, restoredData: any) {
|
if (savedPageState !== null) {
|
||||||
this.load(data, restoredData)
|
const pageState = JSON.parse(decompress(savedPageState))
|
||||||
|
pageState.selectedIds = new Set([])
|
||||||
for (let key in restoredData.document.pages) {
|
data.pageStates[pageState.id] = pageState
|
||||||
this.savePage(restoredData, restoredData.document.id, key)
|
data.currentPageId = pageState.id
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loadPage(data, data.currentPageId)
|
// 4. Save the current document
|
||||||
this.saveToLocalStorage(data, data.document.id)
|
// The document is now "full" and ready. Whether we've restored a
|
||||||
|
// document or created a new one, save the entire current document.
|
||||||
|
localStorage.setItem(
|
||||||
|
storageId(data.document.id, 'document', data.document.id),
|
||||||
|
compress(JSON.stringify(data.document))
|
||||||
|
)
|
||||||
|
|
||||||
|
// 4.1
|
||||||
|
// Also save out copies of each page separately.
|
||||||
|
Object.values(data.document.pages).forEach((page) => {
|
||||||
|
// Save page
|
||||||
|
localStorage.setItem(
|
||||||
|
storageId(data.document.id, 'page', page.id),
|
||||||
|
compress(JSON.stringify(page))
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Save the last page state
|
||||||
|
const currentPageState = data.pageStates[data.currentPageId]
|
||||||
|
|
||||||
|
localStorage.setItem(
|
||||||
|
storageId(data.document.id, 'lastPageState', data.document.id),
|
||||||
|
JSON.stringify(currentPageState)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Finally, save the current document id as the "last opened" document id.
|
||||||
localStorage.setItem(`${CURRENT_VERSION}_lastOpened`, data.document.id)
|
localStorage.setItem(`${CURRENT_VERSION}_lastOpened`, data.document.id)
|
||||||
|
|
||||||
|
// 5. Prepare the new state.
|
||||||
|
// Clear out the other pages from state.
|
||||||
|
Object.values(data.document.pages).forEach((page) => {
|
||||||
|
if (page.id !== data.currentPageId) {
|
||||||
|
page.shapes = {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Update camera for the new page state
|
||||||
|
document.documentElement.style.setProperty(
|
||||||
|
'--camera-zoom',
|
||||||
|
data.pageStates[data.currentPageId].camera.zoom.toString()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
/* ---------------------- Pages --------------------- */
|
/* ---------------------- Pages --------------------- */
|
||||||
|
|
||||||
|
@ -127,25 +212,17 @@ class Storage {
|
||||||
}
|
}
|
||||||
|
|
||||||
savePage(data: Data, fileId = data.document.id, pageId = data.currentPageId) {
|
savePage(data: Data, fileId = data.document.id, pageId = data.currentPageId) {
|
||||||
if (typeof window === 'undefined') return
|
const page = data.document.pages[pageId]
|
||||||
if (typeof localStorage === 'undefined') return
|
|
||||||
|
|
||||||
// Save page
|
// Save page
|
||||||
const page = data.document.pages[pageId]
|
|
||||||
const json = JSON.stringify(page)
|
|
||||||
|
|
||||||
localStorage.setItem(storageId(fileId, 'page', pageId), compress(json))
|
localStorage.setItem(
|
||||||
|
storageId(fileId, 'page', pageId),
|
||||||
|
compress(JSON.stringify(page))
|
||||||
|
)
|
||||||
|
|
||||||
// Save page state
|
// Save page state
|
||||||
|
let currentPageState = data.pageStates[pageId]
|
||||||
let currentPageState = {
|
|
||||||
camera: {
|
|
||||||
point: [0, 0],
|
|
||||||
zoom: 1,
|
|
||||||
},
|
|
||||||
selectedIds: new Set([]),
|
|
||||||
...data.pageStates[pageId],
|
|
||||||
}
|
|
||||||
|
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
storageId(fileId, 'pageState', pageId),
|
storageId(fileId, 'pageState', pageId),
|
||||||
|
@ -156,37 +233,42 @@ class Storage {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
loadPage(data: Data, pageId = data.currentPageId) {
|
loadPage(data: Data, fileId = data.document.id, pageId = data.currentPageId) {
|
||||||
if (typeof window === 'undefined') return
|
if (typeof window === 'undefined') return
|
||||||
if (typeof localStorage === 'undefined') return
|
if (typeof localStorage === 'undefined') return
|
||||||
|
|
||||||
const fileId = data.document.id
|
data.currentPageId = pageId
|
||||||
|
|
||||||
// Page
|
// Get saved page from local storage
|
||||||
const savedPage = localStorage.getItem(storageId(fileId, 'page', pageId))
|
const savedPage = localStorage.getItem(storageId(fileId, 'page', pageId))
|
||||||
|
|
||||||
if (savedPage !== null) {
|
if (savedPage !== null) {
|
||||||
|
// If we have a page, move it into state
|
||||||
data.document.pages[pageId] = JSON.parse(decompress(savedPage))
|
data.document.pages[pageId] = JSON.parse(decompress(savedPage))
|
||||||
} else {
|
} else {
|
||||||
|
// If we don't have a page, create a new page
|
||||||
data.document.pages[pageId] = {
|
data.document.pages[pageId] = {
|
||||||
id: pageId,
|
id: pageId,
|
||||||
type: 'page',
|
type: 'page',
|
||||||
childIndex: 0,
|
childIndex: Object.keys(data.document.pages).length,
|
||||||
name: 'Page',
|
name: 'New Page',
|
||||||
shapes: {},
|
shapes: {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Page state
|
// Get saved page state from local storage
|
||||||
const savedPageState = localStorage.getItem(
|
const savedPageState = localStorage.getItem(
|
||||||
storageId(fileId, 'pageState', pageId)
|
storageId(fileId, 'pageState', pageId)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (savedPageState !== null) {
|
if (savedPageState !== null) {
|
||||||
|
// If we have a page, move it into state
|
||||||
const restored: PageState = JSON.parse(savedPageState)
|
const restored: PageState = JSON.parse(savedPageState)
|
||||||
data.pageStates[pageId] = restored
|
data.pageStates[pageId] = restored
|
||||||
|
data.pageStates[pageId].selectedIds = new Set(restored.selectedIds)
|
||||||
} else {
|
} else {
|
||||||
data.pageStates[pageId] = {
|
data.pageStates[pageId] = {
|
||||||
|
id: pageId,
|
||||||
camera: {
|
camera: {
|
||||||
point: [0, 0],
|
point: [0, 0],
|
||||||
zoom: 1,
|
zoom: 1,
|
||||||
|
@ -195,17 +277,20 @@ class Storage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty shapes in state for other pages
|
// Save the last page state
|
||||||
|
localStorage.setItem(
|
||||||
|
storageId(fileId, 'lastPageState'),
|
||||||
|
JSON.stringify(data.pageStates[pageId])
|
||||||
|
)
|
||||||
|
|
||||||
for (let key in data.document.pages) {
|
// Prepare new state
|
||||||
if (key === pageId) continue
|
|
||||||
data.document.pages[key].shapes = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force selected Ids into sets
|
// Now clear out the other pages from state.
|
||||||
for (let key in data.pageStates) {
|
Object.values(data.document.pages).forEach((page) => {
|
||||||
data.pageStates[key].selectedIds = new Set([])
|
if (page.id !== data.currentPageId) {
|
||||||
}
|
page.shapes = {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Update camera for the new page state
|
// Update camera for the new page state
|
||||||
document.documentElement.style.setProperty(
|
document.documentElement.style.setProperty(
|
||||||
|
@ -217,21 +302,32 @@ class Storage {
|
||||||
/* ------------------- File System ------------------ */
|
/* ------------------- File System ------------------ */
|
||||||
|
|
||||||
saveToFileSystem = (data: Data) => {
|
saveToFileSystem = (data: Data) => {
|
||||||
|
this.saveDocumentToLocalStorage(data)
|
||||||
this.saveDataToFileSystem(data, data.document.id, false)
|
this.saveDataToFileSystem(data, data.document.id, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
saveAsToFileSystem = (data: Data) => {
|
saveAsToFileSystem = (data: Data) => {
|
||||||
this.saveDataToFileSystem(data, uuid(), true)
|
this.saveDocumentToLocalStorage(data)
|
||||||
|
this.saveDataToFileSystem(data, uniqueId(), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
saveDataToFileSystem = (data: Data, fileId: string, saveAs: boolean) => {
|
saveDataToFileSystem = (data: Data, fileId: string, saveAs: boolean) => {
|
||||||
const json = this.getDataToSave(data)
|
const document = this.getCompleteDocument(data)
|
||||||
|
|
||||||
this.saveToLocalStorage(data, fileId)
|
// Then save to file system
|
||||||
|
const blob = new Blob(
|
||||||
const blob = new Blob([json], {
|
[
|
||||||
type: 'application/vnd.tldraw+json',
|
compress(
|
||||||
})
|
JSON.stringify({
|
||||||
|
document,
|
||||||
|
pageState: data.pageStates[data.currentPageId],
|
||||||
|
})
|
||||||
|
),
|
||||||
|
],
|
||||||
|
{
|
||||||
|
type: 'application/vnd.tldraw+json',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
fa.fileSave(
|
fa.fileSave(
|
||||||
blob,
|
blob,
|
||||||
|
@ -258,23 +354,15 @@ class Storage {
|
||||||
}
|
}
|
||||||
|
|
||||||
loadDocumentFromFilesystem() {
|
loadDocumentFromFilesystem() {
|
||||||
console.warn('Loading file from file system.')
|
|
||||||
fa.fileOpen({
|
fa.fileOpen({
|
||||||
description: 'tldraw files',
|
description: 'tldraw files',
|
||||||
})
|
})
|
||||||
.then((blob) =>
|
.then((blob) =>
|
||||||
getTextFromBlob(blob).then((text) => {
|
getTextFromBlob(blob).then((json) => {
|
||||||
const restoredData = JSON.parse(text)
|
|
||||||
|
|
||||||
if (restoredData === null) {
|
|
||||||
console.warn('Could not load that data.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save blob for future saves
|
// Save blob for future saves
|
||||||
this.previousSaveHandle = blob.handle
|
this.previousSaveHandle = blob.handle
|
||||||
|
|
||||||
state.send('LOADED_FROM_FILE', { restoredData: { ...restoredData } })
|
state.send('LOADED_FROM_FILE', { json: decompress(json) })
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
|
|
1
todo.md
1
todo.md
|
@ -30,6 +30,7 @@
|
||||||
|
|
||||||
- [x] Create context Menu
|
- [x] Create context Menu
|
||||||
- [x] Wire up events
|
- [x] Wire up events
|
||||||
|
- [ ] Use nested context menu
|
||||||
|
|
||||||
## Move to Page
|
## Move to Page
|
||||||
|
|
||||||
|
|
3
types.ts
3
types.ts
|
@ -1,7 +1,5 @@
|
||||||
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'
|
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'
|
||||||
|
|
||||||
import React from 'react'
|
|
||||||
|
|
||||||
/* -------------------------------------------------- */
|
/* -------------------------------------------------- */
|
||||||
/* Client State */
|
/* Client State */
|
||||||
/* -------------------------------------------------- */
|
/* -------------------------------------------------- */
|
||||||
|
@ -53,6 +51,7 @@ export interface Page {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PageState {
|
export interface PageState {
|
||||||
|
id: string
|
||||||
selectedIds: Set<string>
|
selectedIds: Set<string>
|
||||||
camera: {
|
camera: {
|
||||||
point: number[]
|
point: number[]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Bounds } from "types"
|
import { Bounds } from 'types'
|
||||||
import * as vec from "./vec"
|
import vec from './vec'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get whether a point is inside of a bounds.
|
* Get whether a point is inside of a bounds.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Bounds } from 'types'
|
import { Bounds } from 'types'
|
||||||
import * as vec from 'utils/vec'
|
import vec from 'utils/vec'
|
||||||
import { isAngleBetween } from './utils'
|
import { isAngleBetween } from './utils'
|
||||||
|
|
||||||
interface Intersection {
|
interface Intersection {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Some helpers for drawing SVGs.
|
// Some helpers for drawing SVGs.
|
||||||
|
|
||||||
import * as vec from './vec'
|
import vec from './vec'
|
||||||
import { getSweep } from 'utils/utils'
|
import { getSweep } from 'utils/utils'
|
||||||
|
|
||||||
// General
|
// General
|
||||||
|
|
|
@ -1,19 +1,15 @@
|
||||||
import Vector from 'lib/code/vector'
|
import Vector from 'lib/code/vector'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {
|
import { Data, Bounds, Edge, Corner, Shape, GroupShape, ShapeType } from 'types'
|
||||||
Data,
|
import { v4 as uuid } from 'uuid'
|
||||||
Bounds,
|
import vec from './vec'
|
||||||
Edge,
|
|
||||||
Corner,
|
|
||||||
Shape,
|
|
||||||
ShapeStyles,
|
|
||||||
GroupShape,
|
|
||||||
ShapeType,
|
|
||||||
} from 'types'
|
|
||||||
import * as vec from './vec'
|
|
||||||
import _isMobile from 'ismobilejs'
|
import _isMobile from 'ismobilejs'
|
||||||
import { getShapeUtils } from 'lib/shape-utils'
|
import { getShapeUtils } from 'lib/shape-utils'
|
||||||
|
|
||||||
|
export function uniqueId() {
|
||||||
|
return uuid()
|
||||||
|
}
|
||||||
|
|
||||||
export function screenToWorld(point: number[], data: Data) {
|
export function screenToWorld(point: number[], data: Data) {
|
||||||
const camera = getCurrentCamera(data)
|
const camera = getCurrentCamera(data)
|
||||||
return vec.sub(vec.div(point, camera.zoom), camera.point)
|
return vec.sub(vec.div(point, camera.zoom), camera.point)
|
||||||
|
|
956
utils/vec.ts
956
utils/vec.ts
|
@ -1,483 +1,487 @@
|
||||||
/**
|
// A big collection of vector utilities. Collected into a class to improve logging / packaging.
|
||||||
* Clamp a value into a range.
|
|
||||||
* @param n
|
|
||||||
* @param min
|
|
||||||
*/
|
|
||||||
export function clamp(n: number, min: number): number
|
|
||||||
export function clamp(n: number, min: number, max: number): number
|
|
||||||
export function clamp(n: number, min: number, max?: number): number {
|
|
||||||
return Math.max(min, typeof max !== 'undefined' ? Math.min(n, max) : n)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
export default class Vec {
|
||||||
* Negate a vector.
|
/**
|
||||||
* @param A
|
* Clamp a value into a range.
|
||||||
*/
|
* @param n
|
||||||
export function neg(A: number[]) {
|
* @param min
|
||||||
return [-A[0], -A[1]]
|
*/
|
||||||
}
|
static clamp(n: number, min: number): number
|
||||||
|
static clamp(n: number, min: number, max: number): number
|
||||||
/**
|
static clamp(n: number, min: number, max?: number): number {
|
||||||
* Add vectors.
|
return Math.max(min, typeof max !== 'undefined' ? Math.min(n, max) : n)
|
||||||
* @param A
|
|
||||||
* @param B
|
|
||||||
*/
|
|
||||||
export function add(A: number[], B: number[]) {
|
|
||||||
return [A[0] + B[0], A[1] + B[1]]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add scalar to vector.
|
|
||||||
* @param A
|
|
||||||
* @param B
|
|
||||||
*/
|
|
||||||
export function addScalar(A: number[], n: number) {
|
|
||||||
return [A[0] + n, A[1] + n]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subtract vectors.
|
|
||||||
* @param A
|
|
||||||
* @param B
|
|
||||||
*/
|
|
||||||
export function sub(A: number[], B: number[]) {
|
|
||||||
return [A[0] - B[0], A[1] - B[1]]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subtract scalar from vector.
|
|
||||||
* @param A
|
|
||||||
* @param B
|
|
||||||
*/
|
|
||||||
export function subScalar(A: number[], n: number) {
|
|
||||||
return [A[0] - n, A[1] - n]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the vector from vectors A to B.
|
|
||||||
* @param A
|
|
||||||
* @param B
|
|
||||||
*/
|
|
||||||
export function vec(A: number[], B: number[]) {
|
|
||||||
// A, B as vectors get the vector from A to B
|
|
||||||
return [B[0] - A[0], B[1] - A[1]]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Vector multiplication by scalar
|
|
||||||
* @param A
|
|
||||||
* @param n
|
|
||||||
*/
|
|
||||||
export function mul(A: number[], n: number) {
|
|
||||||
return [A[0] * n, A[1] * n]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function mulV(A: number[], B: number[]) {
|
|
||||||
return [A[0] * B[0], A[1] * B[1]]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Vector division by scalar.
|
|
||||||
* @param A
|
|
||||||
* @param n
|
|
||||||
*/
|
|
||||||
export function div(A: number[], n: number) {
|
|
||||||
return [A[0] / n, A[1] / n]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Vector division by vector.
|
|
||||||
* @param A
|
|
||||||
* @param n
|
|
||||||
*/
|
|
||||||
export function divV(A: number[], B: number[]) {
|
|
||||||
return [A[0] / B[0], A[1] / B[1]]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Perpendicular rotation of a vector A
|
|
||||||
* @param A
|
|
||||||
*/
|
|
||||||
export function per(A: number[]) {
|
|
||||||
return [A[1], -A[0]]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dot product
|
|
||||||
* @param A
|
|
||||||
* @param B
|
|
||||||
*/
|
|
||||||
export function dpr(A: number[], B: number[]) {
|
|
||||||
return A[0] * B[0] + A[1] * B[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cross product (outer product) | A X B |
|
|
||||||
* @param A
|
|
||||||
* @param B
|
|
||||||
*/
|
|
||||||
export function cpr(A: number[], B: number[]) {
|
|
||||||
return A[0] * B[1] - B[0] * A[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Length of the vector squared
|
|
||||||
* @param A
|
|
||||||
*/
|
|
||||||
export function len2(A: number[]) {
|
|
||||||
return A[0] * A[0] + A[1] * A[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Length of the vector
|
|
||||||
* @param A
|
|
||||||
*/
|
|
||||||
export function len(A: number[]) {
|
|
||||||
return Math.hypot(A[0], A[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Project A over B
|
|
||||||
* @param A
|
|
||||||
* @param B
|
|
||||||
*/
|
|
||||||
export function pry(A: number[], B: number[]) {
|
|
||||||
return dpr(A, B) / len(B)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get normalized / unit vector.
|
|
||||||
* @param A
|
|
||||||
*/
|
|
||||||
export function uni(A: number[]) {
|
|
||||||
return div(A, len(A))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get normalized / unit vector.
|
|
||||||
* @param A
|
|
||||||
*/
|
|
||||||
export function normalize(A: number[]) {
|
|
||||||
return uni(A)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the tangent between two vectors.
|
|
||||||
* @param A
|
|
||||||
* @param B
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export function tangent(A: number[], B: number[]) {
|
|
||||||
return normalize(sub(A, B))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dist length from A to B squared.
|
|
||||||
* @param A
|
|
||||||
* @param B
|
|
||||||
*/
|
|
||||||
export function dist2(A: number[], B: number[]) {
|
|
||||||
return len2(sub(A, B))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dist length from A to B
|
|
||||||
* @param A
|
|
||||||
* @param B
|
|
||||||
*/
|
|
||||||
export function dist(A: number[], B: number[]) {
|
|
||||||
return Math.hypot(A[1] - B[1], A[0] - B[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A faster, though less accurate method for testing distances. Maybe faster?
|
|
||||||
* @param A
|
|
||||||
* @param B
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export function fastDist(A: number[], B: number[]) {
|
|
||||||
const V = [B[0] - A[0], B[1] - A[1]]
|
|
||||||
const aV = [Math.abs(V[0]), Math.abs(V[1])]
|
|
||||||
let r = 1 / Math.max(aV[0], aV[1])
|
|
||||||
r = r * (1.29289 - (aV[0] + aV[1]) * r * 0.29289)
|
|
||||||
return [V[0] * r, V[1] * r]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Angle between vector A and vector B in radians
|
|
||||||
* @param A
|
|
||||||
* @param B
|
|
||||||
*/
|
|
||||||
export function ang(A: number[], B: number[]) {
|
|
||||||
return Math.atan2(cpr(A, B), dpr(A, B))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Angle between vector A and vector B in radians
|
|
||||||
* @param A
|
|
||||||
* @param B
|
|
||||||
*/
|
|
||||||
export function angle(A: number[], B: number[]) {
|
|
||||||
return Math.atan2(B[1] - A[1], B[0] - A[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mean between two vectors or mid vector between two vectors
|
|
||||||
* @param A
|
|
||||||
* @param B
|
|
||||||
*/
|
|
||||||
export function med(A: number[], B: number[]) {
|
|
||||||
return mul(add(A, B), 0.5)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Vector rotation by r (radians)
|
|
||||||
* @param A
|
|
||||||
* @param r rotation in radians
|
|
||||||
*/
|
|
||||||
export function rot(A: number[], r: number) {
|
|
||||||
return [
|
|
||||||
A[0] * Math.cos(r) - A[1] * Math.sin(r),
|
|
||||||
A[0] * Math.sin(r) + A[1] * Math.cos(r),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rotate a vector around another vector by r (radians)
|
|
||||||
* @param A vector
|
|
||||||
* @param C center
|
|
||||||
* @param r rotation in radians
|
|
||||||
*/
|
|
||||||
export function rotWith(A: number[], C: number[], r: number) {
|
|
||||||
if (r === 0) return A
|
|
||||||
|
|
||||||
const s = Math.sin(r)
|
|
||||||
const c = Math.cos(r)
|
|
||||||
|
|
||||||
const px = A[0] - C[0]
|
|
||||||
const py = A[1] - C[1]
|
|
||||||
|
|
||||||
const nx = px * c - py * s
|
|
||||||
const ny = px * s + py * c
|
|
||||||
|
|
||||||
return [nx + C[0], ny + C[1]]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check of two vectors are identical.
|
|
||||||
* @param A
|
|
||||||
* @param B
|
|
||||||
*/
|
|
||||||
export function isEqual(A: number[], B: number[]) {
|
|
||||||
return A[0] === B[0] && A[1] === B[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interpolate vector A to B with a scalar t
|
|
||||||
* @param A
|
|
||||||
* @param B
|
|
||||||
* @param t scalar
|
|
||||||
*/
|
|
||||||
export function lrp(A: number[], B: number[], t: number) {
|
|
||||||
return add(A, mul(vec(A, B), t))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interpolate from A to B when curVAL goes fromVAL => to
|
|
||||||
* @param A
|
|
||||||
* @param B
|
|
||||||
* @param from Starting value
|
|
||||||
* @param to Ending value
|
|
||||||
* @param s Strength
|
|
||||||
*/
|
|
||||||
export function int(A: number[], B: number[], from: number, to: number, s = 1) {
|
|
||||||
const t = (clamp(from, to) - from) / (to - from)
|
|
||||||
return add(mul(A, 1 - t), mul(B, s))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the angle between the three vectors A, B, and C.
|
|
||||||
* @param p1
|
|
||||||
* @param pc
|
|
||||||
* @param p2
|
|
||||||
*/
|
|
||||||
export function ang3(p1: number[], pc: number[], p2: number[]) {
|
|
||||||
// this,
|
|
||||||
const v1 = vec(pc, p1)
|
|
||||||
const v2 = vec(pc, p2)
|
|
||||||
return ang(v1, v2)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Absolute value of a vector.
|
|
||||||
* @param A
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export function abs(A: number[]) {
|
|
||||||
return [Math.abs(A[0]), Math.abs(A[1])]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function rescale(a: number[], n: number) {
|
|
||||||
const l = len(a)
|
|
||||||
return [(n * a[0]) / l, (n * a[1]) / l]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get whether p1 is left of p2, relative to pc.
|
|
||||||
* @param p1
|
|
||||||
* @param pc
|
|
||||||
* @param p2
|
|
||||||
*/
|
|
||||||
export function isLeft(p1: number[], pc: number[], p2: number[]) {
|
|
||||||
// isLeft: >0 for counterclockwise
|
|
||||||
// =0 for none (degenerate)
|
|
||||||
// <0 for clockwise
|
|
||||||
return (pc[0] - p1[0]) * (p2[1] - p1[1]) - (p2[0] - p1[0]) * (pc[1] - p1[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
export function clockwise(p1: number[], pc: number[], p2: number[]) {
|
|
||||||
return isLeft(p1, pc, p2) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
export function round(a: number[], d = 5) {
|
|
||||||
return a.map((v) => Number(v.toPrecision(d)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the minimum distance from a point P to a line with a segment AB.
|
|
||||||
* @param A The start of the line.
|
|
||||||
* @param B The end of the line.
|
|
||||||
* @param P A point.
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
// export function distanceToLine(A: number[], B: number[], P: number[]) {
|
|
||||||
// const delta = sub(B, A)
|
|
||||||
// const angle = Math.atan2(delta[1], delta[0])
|
|
||||||
// const dir = rot(sub(P, A), -angle)
|
|
||||||
// return dir[1]
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the nearest point on a line segment AB.
|
|
||||||
* @param A The start of the line.
|
|
||||||
* @param B The end of the line.
|
|
||||||
* @param P A point.
|
|
||||||
* @param clamp Whether to clamp the resulting point to the segment.
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
// export function nearestPointOnLine(
|
|
||||||
// A: number[],
|
|
||||||
// B: number[],
|
|
||||||
// P: number[],
|
|
||||||
// clamp = true
|
|
||||||
// ) {
|
|
||||||
// const delta = sub(B, A)
|
|
||||||
// const length = len(delta)
|
|
||||||
// const angle = Math.atan2(delta[1], delta[0])
|
|
||||||
// const dir = rot(sub(P, A), -angle)
|
|
||||||
|
|
||||||
// if (clamp) {
|
|
||||||
// if (dir[0] < 0) return A
|
|
||||||
// if (dir[0] > length) return B
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return add(A, div(mul(delta, dir[0]), length))
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the nearest point on a line with a known unit vector that passes through point A
|
|
||||||
* @param A Any point on the line
|
|
||||||
* @param u The unit vector for the line.
|
|
||||||
* @param P A point not on the line to test.
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export function nearestPointOnLineThroughPoint(
|
|
||||||
A: number[],
|
|
||||||
u: number[],
|
|
||||||
P: number[]
|
|
||||||
) {
|
|
||||||
return add(A, mul(u, pry(sub(P, A), u)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Distance between a point and a line with a known unit vector that passes through a point.
|
|
||||||
* @param A Any point on the line
|
|
||||||
* @param u The unit vector for the line.
|
|
||||||
* @param P A point not on the line to test.
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export function distanceToLineThroughPoint(
|
|
||||||
A: number[],
|
|
||||||
u: number[],
|
|
||||||
P: number[]
|
|
||||||
) {
|
|
||||||
return dist(P, nearestPointOnLineThroughPoint(A, u, P))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the nearest point on a line segment between A and B
|
|
||||||
* @param A The start of the line segment
|
|
||||||
* @param B The end of the line segment
|
|
||||||
* @param P The off-line point
|
|
||||||
* @param clamp Whether to clamp the point between A and B.
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export function nearestPointOnLineSegment(
|
|
||||||
A: number[],
|
|
||||||
B: number[],
|
|
||||||
P: number[],
|
|
||||||
clamp = true
|
|
||||||
) {
|
|
||||||
const delta = sub(B, A)
|
|
||||||
const length = len(delta)
|
|
||||||
const u = div(delta, length)
|
|
||||||
|
|
||||||
const pt = add(A, mul(u, pry(sub(P, A), u)))
|
|
||||||
|
|
||||||
if (clamp) {
|
|
||||||
const da = dist(A, pt)
|
|
||||||
const db = dist(B, pt)
|
|
||||||
|
|
||||||
if (db < da && da > length) return B
|
|
||||||
if (da < db && db > length) return A
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return pt
|
/**
|
||||||
}
|
* Negate a vector.
|
||||||
|
* @param A
|
||||||
|
*/
|
||||||
|
static neg = (A: number[]) => {
|
||||||
|
return [-A[0], -A[1]]
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Distance between a point and the nearest point on a line segment between A and B
|
* Add vectors.
|
||||||
* @param A The start of the line segment
|
* @param A
|
||||||
* @param B The end of the line segment
|
* @param B
|
||||||
* @param P The off-line point
|
*/
|
||||||
* @param clamp Whether to clamp the point between A and B.
|
static add = (A: number[], B: number[]) => {
|
||||||
* @returns
|
return [A[0] + B[0], A[1] + B[1]]
|
||||||
*/
|
}
|
||||||
export function distanceToLineSegment(
|
|
||||||
A: number[],
|
|
||||||
B: number[],
|
|
||||||
P: number[],
|
|
||||||
clamp = true
|
|
||||||
) {
|
|
||||||
return dist(P, nearestPointOnLineSegment(A, B, P, clamp))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a vector d distance from A towards B.
|
* Add scalar to vector.
|
||||||
* @param A
|
* @param A
|
||||||
* @param B
|
* @param B
|
||||||
* @param d
|
*/
|
||||||
* @returns
|
static addScalar = (A: number[], n: number) => {
|
||||||
*/
|
return [A[0] + n, A[1] + n]
|
||||||
export function nudge(A: number[], B: number[], d: number) {
|
}
|
||||||
return add(A, mul(uni(vec(A, B)), d))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Round a vector to a precision length.
|
* Subtract vectors.
|
||||||
* @param a
|
* @param A
|
||||||
* @param n
|
* @param B
|
||||||
*/
|
*/
|
||||||
export function toPrecision(a: number[], n = 4) {
|
static sub = (A: number[], B: number[]) => {
|
||||||
return [+a[0].toPrecision(n), +a[1].toPrecision(n)]
|
return [A[0] - B[0], A[1] - B[1]]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subtract scalar from vector.
|
||||||
|
* @param A
|
||||||
|
* @param B
|
||||||
|
*/
|
||||||
|
static subScalar = (A: number[], n: number) => {
|
||||||
|
return [A[0] - n, A[1] - n]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the vector from vectors A to B.
|
||||||
|
* @param A
|
||||||
|
* @param B
|
||||||
|
*/
|
||||||
|
static vec = (A: number[], B: number[]) => {
|
||||||
|
// A, B as vectors get the vector from A to B
|
||||||
|
return [B[0] - A[0], B[1] - A[1]]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vector multiplication by scalar
|
||||||
|
* @param A
|
||||||
|
* @param n
|
||||||
|
*/
|
||||||
|
static mul = (A: number[], n: number) => {
|
||||||
|
return [A[0] * n, A[1] * n]
|
||||||
|
}
|
||||||
|
|
||||||
|
static mulV = (A: number[], B: number[]) => {
|
||||||
|
return [A[0] * B[0], A[1] * B[1]]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vector division by scalar.
|
||||||
|
* @param A
|
||||||
|
* @param n
|
||||||
|
*/
|
||||||
|
static div = (A: number[], n: number) => {
|
||||||
|
return [A[0] / n, A[1] / n]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vector division by vector.
|
||||||
|
* @param A
|
||||||
|
* @param n
|
||||||
|
*/
|
||||||
|
static divV = (A: number[], B: number[]) => {
|
||||||
|
return [A[0] / B[0], A[1] / B[1]]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perpendicular rotation of a vector A
|
||||||
|
* @param A
|
||||||
|
*/
|
||||||
|
static per(A: number[]) {
|
||||||
|
return [A[1], -A[0]]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dot product
|
||||||
|
* @param A
|
||||||
|
* @param B
|
||||||
|
*/
|
||||||
|
static dpr = (A: number[], B: number[]) => {
|
||||||
|
return A[0] * B[0] + A[1] * B[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cross product (outer product) | A X B |
|
||||||
|
* @param A
|
||||||
|
* @param B
|
||||||
|
*/
|
||||||
|
static cpr = (A: number[], B: number[]) => {
|
||||||
|
return A[0] * B[1] - B[0] * A[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Length of the vector squared
|
||||||
|
* @param A
|
||||||
|
*/
|
||||||
|
static len2 = (A: number[]) => {
|
||||||
|
return A[0] * A[0] + A[1] * A[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Length of the vector
|
||||||
|
* @param A
|
||||||
|
*/
|
||||||
|
static len = (A: number[]) => {
|
||||||
|
return Math.hypot(A[0], A[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project A over B
|
||||||
|
* @param A
|
||||||
|
* @param B
|
||||||
|
*/
|
||||||
|
static pry = (A: number[], B: number[]) => {
|
||||||
|
return Vec.dpr(A, B) / Vec.len(B)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get normalized / unit vector.
|
||||||
|
* @param A
|
||||||
|
*/
|
||||||
|
static uni = (A: number[]) => {
|
||||||
|
return Vec.div(A, Vec.len(A))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get normalized / unit vector.
|
||||||
|
* @param A
|
||||||
|
*/
|
||||||
|
static normalize = (A: number[]) => {
|
||||||
|
return Vec.uni(A)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the tangent between two vectors.
|
||||||
|
* @param A
|
||||||
|
* @param B
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
static tangent = (A: number[], B: number[]) => {
|
||||||
|
return Vec.normalize(Vec.sub(A, B))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dist length from A to B squared.
|
||||||
|
* @param A
|
||||||
|
* @param B
|
||||||
|
*/
|
||||||
|
static dist2 = (A: number[], B: number[]) => {
|
||||||
|
return Vec.len2(Vec.sub(A, B))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dist length from A to B
|
||||||
|
* @param A
|
||||||
|
* @param B
|
||||||
|
*/
|
||||||
|
static dist = (A: number[], B: number[]) => {
|
||||||
|
return Math.hypot(A[1] - B[1], A[0] - B[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A faster, though less accurate method for testing distances. Maybe faster?
|
||||||
|
* @param A
|
||||||
|
* @param B
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
static fastDist = (A: number[], B: number[]) => {
|
||||||
|
const V = [B[0] - A[0], B[1] - A[1]]
|
||||||
|
const aV = [Math.abs(V[0]), Math.abs(V[1])]
|
||||||
|
let r = 1 / Math.max(aV[0], aV[1])
|
||||||
|
r = r * (1.29289 - (aV[0] + aV[1]) * r * 0.29289)
|
||||||
|
return [V[0] * r, V[1] * r]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Angle between vector A and vector B in radians
|
||||||
|
* @param A
|
||||||
|
* @param B
|
||||||
|
*/
|
||||||
|
static ang = (A: number[], B: number[]) => {
|
||||||
|
return Math.atan2(Vec.cpr(A, B), Vec.dpr(A, B))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Angle between vector A and vector B in radians
|
||||||
|
* @param A
|
||||||
|
* @param B
|
||||||
|
*/
|
||||||
|
static angle = (A: number[], B: number[]) => {
|
||||||
|
return Math.atan2(B[1] - A[1], B[0] - A[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mean between two vectors or mid vector between two vectors
|
||||||
|
* @param A
|
||||||
|
* @param B
|
||||||
|
*/
|
||||||
|
static med = (A: number[], B: number[]) => {
|
||||||
|
return Vec.mul(Vec.add(A, B), 0.5)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vector rotation by r (radians)
|
||||||
|
* @param A
|
||||||
|
* @param r rotation in radians
|
||||||
|
*/
|
||||||
|
static rot = (A: number[], r: number) => {
|
||||||
|
return [
|
||||||
|
A[0] * Math.cos(r) - A[1] * Math.sin(r),
|
||||||
|
A[0] * Math.sin(r) + A[1] * Math.cos(r),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotate a vector around another vector by r (radians)
|
||||||
|
* @param A vector
|
||||||
|
* @param C center
|
||||||
|
* @param r rotation in radians
|
||||||
|
*/
|
||||||
|
static rotWith = (A: number[], C: number[], r: number) => {
|
||||||
|
if (r === 0) return A
|
||||||
|
|
||||||
|
const s = Math.sin(r)
|
||||||
|
const c = Math.cos(r)
|
||||||
|
|
||||||
|
const px = A[0] - C[0]
|
||||||
|
const py = A[1] - C[1]
|
||||||
|
|
||||||
|
const nx = px * c - py * s
|
||||||
|
const ny = px * s + py * c
|
||||||
|
|
||||||
|
return [nx + C[0], ny + C[1]]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check of two vectors are identical.
|
||||||
|
* @param A
|
||||||
|
* @param B
|
||||||
|
*/
|
||||||
|
static isEqual = (A: number[], B: number[]) => {
|
||||||
|
return A[0] === B[0] && A[1] === B[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interpolate vector A to B with a scalar t
|
||||||
|
* @param A
|
||||||
|
* @param B
|
||||||
|
* @param t scalar
|
||||||
|
*/
|
||||||
|
static lrp = (A: number[], B: number[], t: number) => {
|
||||||
|
return Vec.add(A, Vec.mul(Vec.vec(A, B), t))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interpolate from A to B when curVAL goes fromVAL => to
|
||||||
|
* @param A
|
||||||
|
* @param B
|
||||||
|
* @param from Starting value
|
||||||
|
* @param to Ending value
|
||||||
|
* @param s Strength
|
||||||
|
*/
|
||||||
|
static int = (A: number[], B: number[], from: number, to: number, s = 1) => {
|
||||||
|
const t = (Vec.clamp(from, to) - from) / (to - from)
|
||||||
|
return Vec.add(Vec.mul(A, 1 - t), Vec.mul(B, s))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the angle between the three vectors A, B, and C.
|
||||||
|
* @param p1
|
||||||
|
* @param pc
|
||||||
|
* @param p2
|
||||||
|
*/
|
||||||
|
static ang3 = (p1: number[], pc: number[], p2: number[]) => {
|
||||||
|
// this,
|
||||||
|
const v1 = Vec.vec(pc, p1)
|
||||||
|
const v2 = Vec.vec(pc, p2)
|
||||||
|
return Vec.ang(v1, v2)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Absolute value of a vector.
|
||||||
|
* @param A
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
static abs = (A: number[]) => {
|
||||||
|
return [Math.abs(A[0]), Math.abs(A[1])]
|
||||||
|
}
|
||||||
|
|
||||||
|
static rescale = (a: number[], n: number) => {
|
||||||
|
const l = Vec.len(a)
|
||||||
|
return [(n * a[0]) / l, (n * a[1]) / l]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get whether p1 is left of p2, relative to pc.
|
||||||
|
* @param p1
|
||||||
|
* @param pc
|
||||||
|
* @param p2
|
||||||
|
*/
|
||||||
|
static isLeft = (p1: number[], pc: number[], p2: number[]) => {
|
||||||
|
// isLeft: >0 for counterclockwise
|
||||||
|
// =0 for none (degenerate)
|
||||||
|
// <0 for clockwise
|
||||||
|
return (pc[0] - p1[0]) * (p2[1] - p1[1]) - (p2[0] - p1[0]) * (pc[1] - p1[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
static clockwise = (p1: number[], pc: number[], p2: number[]) => {
|
||||||
|
return Vec.isLeft(p1, pc, p2) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
static round = (a: number[], d = 5) => {
|
||||||
|
return a.map((v) => Number(v.toPrecision(d)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the minimum distance from a point P to a line with a segment AB.
|
||||||
|
* @param A The start of the line.
|
||||||
|
* @param B The end of the line.
|
||||||
|
* @param P A point.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
// static distanceToLine(A: number[], B: number[], P: number[]) {
|
||||||
|
// const delta = sub(B, A)
|
||||||
|
// const angle = Math.atan2(delta[1], delta[0])
|
||||||
|
// const dir = rot(sub(P, A), -angle)
|
||||||
|
// return dir[1]
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the nearest point on a line segment AB.
|
||||||
|
* @param A The start of the line.
|
||||||
|
* @param B The end of the line.
|
||||||
|
* @param P A point.
|
||||||
|
* @param clamp Whether to clamp the resulting point to the segment.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
// static nearestPointOnLine(
|
||||||
|
// A: number[],
|
||||||
|
// B: number[],
|
||||||
|
// P: number[],
|
||||||
|
// clamp = true
|
||||||
|
// ) {
|
||||||
|
// const delta = sub(B, A)
|
||||||
|
// const length = len(delta)
|
||||||
|
// const angle = Math.atan2(delta[1], delta[0])
|
||||||
|
// const dir = rot(sub(P, A), -angle)
|
||||||
|
|
||||||
|
// if (clamp) {
|
||||||
|
// if (dir[0] < 0) return A
|
||||||
|
// if (dir[0] > length) return B
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return add(A, div(mul(delta, dir[0]), length))
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the nearest point on a line with a known unit vector that passes through point A
|
||||||
|
* @param A Any point on the line
|
||||||
|
* @param u The unit vector for the line.
|
||||||
|
* @param P A point not on the line to test.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
static nearestPointOnLineThroughPoint = (
|
||||||
|
A: number[],
|
||||||
|
u: number[],
|
||||||
|
P: number[]
|
||||||
|
) => {
|
||||||
|
return Vec.add(A, Vec.mul(u, Vec.pry(Vec.sub(P, A), u)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Distance between a point and a line with a known unit vector that passes through a point.
|
||||||
|
* @param A Any point on the line
|
||||||
|
* @param u The unit vector for the line.
|
||||||
|
* @param P A point not on the line to test.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
static distanceToLineThroughPoint = (
|
||||||
|
A: number[],
|
||||||
|
u: number[],
|
||||||
|
P: number[]
|
||||||
|
) => {
|
||||||
|
return Vec.dist(P, Vec.nearestPointOnLineThroughPoint(A, u, P))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the nearest point on a line segment between A and B
|
||||||
|
* @param A The start of the line segment
|
||||||
|
* @param B The end of the line segment
|
||||||
|
* @param P The off-line point
|
||||||
|
* @param clamp Whether to clamp the point between A and B.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
static nearestPointOnLineSegment = (
|
||||||
|
A: number[],
|
||||||
|
B: number[],
|
||||||
|
P: number[],
|
||||||
|
clamp = true
|
||||||
|
) => {
|
||||||
|
const delta = Vec.sub(B, A)
|
||||||
|
const length = Vec.len(delta)
|
||||||
|
const u = Vec.div(delta, length)
|
||||||
|
|
||||||
|
const pt = Vec.add(A, Vec.mul(u, Vec.pry(Vec.sub(P, A), u)))
|
||||||
|
|
||||||
|
if (clamp) {
|
||||||
|
const da = Vec.dist(A, pt)
|
||||||
|
const db = Vec.dist(B, pt)
|
||||||
|
|
||||||
|
if (db < da && da > length) return B
|
||||||
|
if (da < db && db > length) return A
|
||||||
|
}
|
||||||
|
|
||||||
|
return pt
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Distance between a point and the nearest point on a line segment between A and B
|
||||||
|
* @param A The start of the line segment
|
||||||
|
* @param B The end of the line segment
|
||||||
|
* @param P The off-line point
|
||||||
|
* @param clamp Whether to clamp the point between A and B.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
static distanceToLineSegment = (
|
||||||
|
A: number[],
|
||||||
|
B: number[],
|
||||||
|
P: number[],
|
||||||
|
clamp = true
|
||||||
|
) => {
|
||||||
|
return Vec.dist(P, Vec.nearestPointOnLineSegment(A, B, P, clamp))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a vector d distance from A towards B.
|
||||||
|
* @param A
|
||||||
|
* @param B
|
||||||
|
* @param d
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
static nudge = (A: number[], B: number[], d: number) => {
|
||||||
|
return Vec.add(A, Vec.mul(Vec.uni(Vec.vec(A, B)), d))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Round a vector to a precision length.
|
||||||
|
* @param a
|
||||||
|
* @param n
|
||||||
|
*/
|
||||||
|
static toPrecision = (a: number[], n = 4) => {
|
||||||
|
return [+a[0].toPrecision(n), +a[1].toPrecision(n)]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
298
yarn.lock
298
yarn.lock
|
@ -1573,38 +1573,40 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.13.10"
|
"@babel/runtime" "^7.13.10"
|
||||||
|
|
||||||
"@radix-ui/react-arrow@0.0.13":
|
"@radix-ui/react-arrow@0.0.14":
|
||||||
version "0.0.13"
|
version "0.0.14"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-0.0.13.tgz#06b5a17f33bbc1af1c88d2827f25f37be95daa38"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-0.0.14.tgz#70b2c66efbf3cde0c9dd0895417e39f6cdf31805"
|
||||||
integrity sha512-BHBAULgQYmj36BrJ+1AGhC5p4QjJaE+szJgJ1a1EYOM3G6QOeIQKYvIm8TPEdKAiJhAivK+jZFccsE4Blzqc9g==
|
integrity sha512-ZWfQM3hTCXN6ub2dMUmMv2ttEB/1zuBlOZdeWFeCCJHwZ+yy7iPzWF6ixaNygBa6t451PImq/dC7sqOnDJCfqQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.13.10"
|
"@babel/runtime" "^7.13.10"
|
||||||
"@radix-ui/react-polymorphic" "0.0.11"
|
"@radix-ui/react-polymorphic" "0.0.12"
|
||||||
"@radix-ui/react-primitive" "0.0.13"
|
"@radix-ui/react-primitive" "0.0.14"
|
||||||
|
|
||||||
"@radix-ui/react-checkbox@^0.0.15":
|
"@radix-ui/react-checkbox@^0.0.16":
|
||||||
version "0.0.15"
|
version "0.0.16"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-checkbox/-/react-checkbox-0.0.15.tgz#d53b56854fbba65e74ed4486116107638951b9d1"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-checkbox/-/react-checkbox-0.0.16.tgz#503a934f73745e9c3831a5fd39d3b06062813f0e"
|
||||||
integrity sha512-R8ErERPlu2kvmqNjxRyyLcS1y3D7J2bQUUEPsvP0BL2AfisUjbT7c9t19k2K/Un3Iieqe93gTPG4LRdbDQQjBw==
|
integrity sha512-2RJep0OB4FZvGk/PTou8BEt/cISA/i88iiloZuAxlOjYgF90W4xfrQqgczLCC64t0ee3DtUkSOJyjO45M95hVg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.13.10"
|
"@babel/runtime" "^7.13.10"
|
||||||
"@radix-ui/primitive" "0.0.5"
|
"@radix-ui/primitive" "0.0.5"
|
||||||
"@radix-ui/react-compose-refs" "0.0.5"
|
"@radix-ui/react-compose-refs" "0.0.5"
|
||||||
"@radix-ui/react-context" "0.0.5"
|
"@radix-ui/react-context" "0.0.5"
|
||||||
"@radix-ui/react-label" "0.0.13"
|
"@radix-ui/react-label" "0.0.14"
|
||||||
"@radix-ui/react-polymorphic" "0.0.11"
|
"@radix-ui/react-polymorphic" "0.0.12"
|
||||||
"@radix-ui/react-presence" "0.0.14"
|
"@radix-ui/react-presence" "0.0.14"
|
||||||
"@radix-ui/react-primitive" "0.0.13"
|
"@radix-ui/react-primitive" "0.0.14"
|
||||||
"@radix-ui/react-use-controllable-state" "0.0.6"
|
"@radix-ui/react-use-controllable-state" "0.0.6"
|
||||||
|
"@radix-ui/react-use-previous" "0.0.5"
|
||||||
|
"@radix-ui/react-use-size" "0.0.6"
|
||||||
|
|
||||||
"@radix-ui/react-collection@0.0.12":
|
"@radix-ui/react-collection@0.0.13":
|
||||||
version "0.0.12"
|
version "0.0.13"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-0.0.12.tgz#5cd09312cdec34fdbbe1d31affaba69eb768e342"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-0.0.13.tgz#050fc9b1d84b58ab04a5852812b181767dde2e85"
|
||||||
integrity sha512-jUP99/wCHpXm7ytbmpcjhhxFHBsr2lptEzKO8DUkD2CrJBS4Q3XHMKBDOvNQQzIqJhsz0A5JP6Fo0Vgd8Ld3FQ==
|
integrity sha512-TKUUFLeRlxp+1S1FQz9pQkP1VbRwZGOEH8oUdgWjUsNc+GVURlS3H5twsDtSAAfvebOc6YmDTE23mvvhfsPhnw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.13.10"
|
"@babel/runtime" "^7.13.10"
|
||||||
"@radix-ui/react-compose-refs" "0.0.5"
|
"@radix-ui/react-compose-refs" "0.0.5"
|
||||||
"@radix-ui/react-slot" "0.0.10"
|
"@radix-ui/react-slot" "0.0.11"
|
||||||
|
|
||||||
"@radix-ui/react-compose-refs@0.0.5":
|
"@radix-ui/react-compose-refs@0.0.5":
|
||||||
version "0.0.5"
|
version "0.0.5"
|
||||||
|
@ -1613,17 +1615,17 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.13.10"
|
"@babel/runtime" "^7.13.10"
|
||||||
|
|
||||||
"@radix-ui/react-context-menu@^0.0.19":
|
"@radix-ui/react-context-menu@^0.0.21":
|
||||||
version "0.0.19"
|
version "0.0.21"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-context-menu/-/react-context-menu-0.0.19.tgz#47ef194d7bc925ff7f998889be9fd373ce4bd9a1"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-context-menu/-/react-context-menu-0.0.21.tgz#b080c8e515b9a0d18fcda9b02f34b7a94347ef08"
|
||||||
integrity sha512-FR2jXeFqxD9n+1AC81+7jfMbnz80kdQNMfgIcPQL1S0M5SODADdDiTKKfok4Blc1nYWe7nEwBTYauMirF7avSQ==
|
integrity sha512-snRhgVDtdxBNwds2ULd7RQAB97PfSeKlrL5rRudlj4YTlLq7y/Jr0oJ7FTnu9EylfH2ii0uKcW5dEVhrbzr5YQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.13.10"
|
"@babel/runtime" "^7.13.10"
|
||||||
"@radix-ui/primitive" "0.0.5"
|
"@radix-ui/primitive" "0.0.5"
|
||||||
"@radix-ui/react-context" "0.0.5"
|
"@radix-ui/react-context" "0.0.5"
|
||||||
"@radix-ui/react-menu" "0.0.18"
|
"@radix-ui/react-menu" "0.0.19"
|
||||||
"@radix-ui/react-polymorphic" "0.0.11"
|
"@radix-ui/react-polymorphic" "0.0.12"
|
||||||
"@radix-ui/react-primitive" "0.0.13"
|
"@radix-ui/react-primitive" "0.0.14"
|
||||||
"@radix-ui/react-use-callback-ref" "0.0.5"
|
"@radix-ui/react-use-callback-ref" "0.0.5"
|
||||||
|
|
||||||
"@radix-ui/react-context@0.0.5":
|
"@radix-ui/react-context@0.0.5":
|
||||||
|
@ -1633,50 +1635,54 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.13.10"
|
"@babel/runtime" "^7.13.10"
|
||||||
|
|
||||||
"@radix-ui/react-dialog@^0.0.17":
|
"@radix-ui/react-dialog@^0.0.18":
|
||||||
version "0.0.17"
|
version "0.0.18"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-0.0.17.tgz#736e37626c4e9e20d46546ddbbb8869a352578f0"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-0.0.18.tgz#8d2d9f8816bb8447056031583a3a3cb2b6305281"
|
||||||
integrity sha512-DOSW8SdniyVLro+MvF0owaEEa8MUYGMuvuuSQpFnD/hA+K0pKzyTSjEuC7OeflPCImBFEbmKhgRoeWypfMZZOA==
|
integrity sha512-EH8yxFh3hQQ/hIPQsBzdJgx3oWTEmLu2a2x2PfRjxbDhcDIjcYJWdeEMjkTUjkBwpz3h6L/JWqnYJ2dqA65Deg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.13.10"
|
"@babel/runtime" "^7.13.10"
|
||||||
"@radix-ui/primitive" "0.0.5"
|
"@radix-ui/primitive" "0.0.5"
|
||||||
"@radix-ui/react-compose-refs" "0.0.5"
|
"@radix-ui/react-compose-refs" "0.0.5"
|
||||||
"@radix-ui/react-context" "0.0.5"
|
"@radix-ui/react-context" "0.0.5"
|
||||||
"@radix-ui/react-dismissable-layer" "0.0.13"
|
"@radix-ui/react-dismissable-layer" "0.0.14"
|
||||||
"@radix-ui/react-focus-guards" "0.0.7"
|
"@radix-ui/react-focus-guards" "0.0.7"
|
||||||
"@radix-ui/react-focus-scope" "0.0.13"
|
"@radix-ui/react-focus-scope" "0.0.14"
|
||||||
"@radix-ui/react-id" "0.0.6"
|
"@radix-ui/react-id" "0.0.6"
|
||||||
"@radix-ui/react-polymorphic" "0.0.11"
|
"@radix-ui/react-polymorphic" "0.0.12"
|
||||||
"@radix-ui/react-portal" "0.0.13"
|
"@radix-ui/react-portal" "0.0.14"
|
||||||
"@radix-ui/react-presence" "0.0.14"
|
"@radix-ui/react-presence" "0.0.14"
|
||||||
"@radix-ui/react-primitive" "0.0.13"
|
"@radix-ui/react-primitive" "0.0.14"
|
||||||
|
"@radix-ui/react-slot" "0.0.11"
|
||||||
"@radix-ui/react-use-controllable-state" "0.0.6"
|
"@radix-ui/react-use-controllable-state" "0.0.6"
|
||||||
aria-hidden "^1.1.1"
|
aria-hidden "^1.1.1"
|
||||||
react-remove-scroll "^2.4.0"
|
react-remove-scroll "^2.4.0"
|
||||||
|
|
||||||
"@radix-ui/react-dismissable-layer@0.0.13":
|
"@radix-ui/react-dismissable-layer@0.0.14":
|
||||||
version "0.0.13"
|
version "0.0.14"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-0.0.13.tgz#7c4be6170a14d8a66c48680a8a8c987bc29bcf05"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-0.0.14.tgz#9d8a3415a2830688070c6596dec18b43c33df7b2"
|
||||||
integrity sha512-g0zhxdZzCJhVeRumaU8ODptRFhYorSRPsLwD30sz9slYaW0yg6lHvMQicRNtgFX0WnsbmrUVY6NMrwWpSHJXbg==
|
integrity sha512-0pmRuGYYvWlEaED1igGFLjic0+hD0OqvsnrZaN3n1nDOkoCd7H5CA2geaShSrlBF5riI2Dr9jIZPGLbDRhs4DA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.13.10"
|
"@babel/runtime" "^7.13.10"
|
||||||
|
"@radix-ui/primitive" "0.0.5"
|
||||||
|
"@radix-ui/react-polymorphic" "0.0.12"
|
||||||
|
"@radix-ui/react-primitive" "0.0.14"
|
||||||
"@radix-ui/react-use-body-pointer-events" "0.0.6"
|
"@radix-ui/react-use-body-pointer-events" "0.0.6"
|
||||||
"@radix-ui/react-use-callback-ref" "0.0.5"
|
"@radix-ui/react-use-callback-ref" "0.0.5"
|
||||||
"@radix-ui/react-use-escape-keydown" "0.0.6"
|
"@radix-ui/react-use-escape-keydown" "0.0.6"
|
||||||
|
|
||||||
"@radix-ui/react-dropdown-menu@^0.0.19":
|
"@radix-ui/react-dropdown-menu@^0.0.20":
|
||||||
version "0.0.19"
|
version "0.0.20"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-0.0.19.tgz#fbc3106bcda65d3060b280f4af3a9c45324ab474"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-0.0.20.tgz#ca4667deb38ff7b74e6741aac1cebd1b50e3ec07"
|
||||||
integrity sha512-7UhT6PIXl9fr8Ai9DkXMtxPNQFh8emrklgY5jcad9NHKWAztgfL6Fm+Ns0GEYUkd5OpJLEaIj/EUkwFM73SrGw==
|
integrity sha512-3MXtFeNkRZxAA32gvAGBdzlmJRMCNgBKWy0rDSd5NNzRjtqNFKZRR5dd1gWQTiS24aLxSiztVc9exnvMx4Lu8g==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.13.10"
|
"@babel/runtime" "^7.13.10"
|
||||||
"@radix-ui/primitive" "0.0.5"
|
"@radix-ui/primitive" "0.0.5"
|
||||||
"@radix-ui/react-compose-refs" "0.0.5"
|
"@radix-ui/react-compose-refs" "0.0.5"
|
||||||
"@radix-ui/react-context" "0.0.5"
|
"@radix-ui/react-context" "0.0.5"
|
||||||
"@radix-ui/react-id" "0.0.6"
|
"@radix-ui/react-id" "0.0.6"
|
||||||
"@radix-ui/react-menu" "0.0.18"
|
"@radix-ui/react-menu" "0.0.19"
|
||||||
"@radix-ui/react-polymorphic" "0.0.11"
|
"@radix-ui/react-polymorphic" "0.0.12"
|
||||||
"@radix-ui/react-primitive" "0.0.13"
|
"@radix-ui/react-primitive" "0.0.14"
|
||||||
"@radix-ui/react-use-controllable-state" "0.0.6"
|
"@radix-ui/react-use-controllable-state" "0.0.6"
|
||||||
|
|
||||||
"@radix-ui/react-focus-guards@0.0.7":
|
"@radix-ui/react-focus-guards@0.0.7":
|
||||||
|
@ -1686,12 +1692,15 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.13.10"
|
"@babel/runtime" "^7.13.10"
|
||||||
|
|
||||||
"@radix-ui/react-focus-scope@0.0.13":
|
"@radix-ui/react-focus-scope@0.0.14":
|
||||||
version "0.0.13"
|
version "0.0.14"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-0.0.13.tgz#06dd6781d457b272601d4c087ac1240907824443"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-0.0.14.tgz#778e2a3ea607621d82e0139616d7ea6d517d9533"
|
||||||
integrity sha512-PelAuc+7HSGruBraSzuHogwaKqCvmO288ecIm3cCAkrJqPQ7hoKSd/LfLfoa/EvjqK9azmm7NQ6LSPoteQvOGQ==
|
integrity sha512-D3v6Tw8vzpIBNd2I32Q2G4LCiXMIlmc6Pl2VV9CZjSatDOjkV/ckGbhkQyQ7QxnD/0CmiSxNo5hTeGRmZDjwmA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.13.10"
|
"@babel/runtime" "^7.13.10"
|
||||||
|
"@radix-ui/react-compose-refs" "0.0.5"
|
||||||
|
"@radix-ui/react-polymorphic" "0.0.12"
|
||||||
|
"@radix-ui/react-primitive" "0.0.14"
|
||||||
"@radix-ui/react-use-callback-ref" "0.0.5"
|
"@radix-ui/react-use-callback-ref" "0.0.5"
|
||||||
|
|
||||||
"@radix-ui/react-icons@^1.0.3":
|
"@radix-ui/react-icons@^1.0.3":
|
||||||
|
@ -1706,72 +1715,72 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.13.10"
|
"@babel/runtime" "^7.13.10"
|
||||||
|
|
||||||
"@radix-ui/react-label@0.0.13":
|
"@radix-ui/react-label@0.0.14":
|
||||||
version "0.0.13"
|
version "0.0.14"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-label/-/react-label-0.0.13.tgz#b71930fa16a2cf859296317436cb88e31efb8ecf"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-label/-/react-label-0.0.14.tgz#32928a0f5d72e3a24b99021f5c6805e96f3d9395"
|
||||||
integrity sha512-csNElm8qA38pOHr772CXIvBXd/eCGaoAMImuLdawUxQNzwxQ4npd8lr/f9fi/4OLkgeNOVOqjsaVamiNmF/lIw==
|
integrity sha512-gtrEpON6Ye8OBYtOEJBEuB/n/h6x07VPs7JR/1t34LaB74GJ+gACcafLcg5gMGWku3Gbc3eaDwfhOkxhTuJdYQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.13.10"
|
"@babel/runtime" "^7.13.10"
|
||||||
"@radix-ui/react-compose-refs" "0.0.5"
|
"@radix-ui/react-compose-refs" "0.0.5"
|
||||||
"@radix-ui/react-id" "0.0.6"
|
"@radix-ui/react-id" "0.0.6"
|
||||||
"@radix-ui/react-polymorphic" "0.0.11"
|
"@radix-ui/react-polymorphic" "0.0.12"
|
||||||
"@radix-ui/react-primitive" "0.0.13"
|
"@radix-ui/react-primitive" "0.0.14"
|
||||||
|
|
||||||
"@radix-ui/react-menu@0.0.18":
|
"@radix-ui/react-menu@0.0.19":
|
||||||
version "0.0.18"
|
version "0.0.19"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-menu/-/react-menu-0.0.18.tgz#b36f7657eb6757c623ffc688c48a4781ffd82351"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-menu/-/react-menu-0.0.19.tgz#12036862ad6c813e442a14316b5ed380f54962e9"
|
||||||
integrity sha512-js5hFzoxNOnHV8g7RPoPl/GncUCW2aCOuNt9Qh6WznRmxmsETPUWZZe4kADJnZmYbIxG07EEl0iv3E1rmsqNMw==
|
integrity sha512-ACGk5i83KahzHGyZVpWGyZBi68VCplQgMmS8ZvuuwDGf1vQl5dlpxvWM8iKh6EUL8zCTXNzX2jRtg0NZYoRrPg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.13.10"
|
"@babel/runtime" "^7.13.10"
|
||||||
"@radix-ui/primitive" "0.0.5"
|
"@radix-ui/primitive" "0.0.5"
|
||||||
"@radix-ui/react-collection" "0.0.12"
|
"@radix-ui/react-collection" "0.0.13"
|
||||||
"@radix-ui/react-compose-refs" "0.0.5"
|
"@radix-ui/react-compose-refs" "0.0.5"
|
||||||
"@radix-ui/react-context" "0.0.5"
|
"@radix-ui/react-context" "0.0.5"
|
||||||
"@radix-ui/react-dismissable-layer" "0.0.13"
|
"@radix-ui/react-dismissable-layer" "0.0.14"
|
||||||
"@radix-ui/react-focus-guards" "0.0.7"
|
"@radix-ui/react-focus-guards" "0.0.7"
|
||||||
"@radix-ui/react-focus-scope" "0.0.13"
|
"@radix-ui/react-focus-scope" "0.0.14"
|
||||||
"@radix-ui/react-polymorphic" "0.0.11"
|
"@radix-ui/react-id" "0.0.6"
|
||||||
"@radix-ui/react-popper" "0.0.16"
|
"@radix-ui/react-polymorphic" "0.0.12"
|
||||||
"@radix-ui/react-portal" "0.0.13"
|
"@radix-ui/react-popper" "0.0.17"
|
||||||
|
"@radix-ui/react-portal" "0.0.14"
|
||||||
"@radix-ui/react-presence" "0.0.14"
|
"@radix-ui/react-presence" "0.0.14"
|
||||||
"@radix-ui/react-primitive" "0.0.13"
|
"@radix-ui/react-primitive" "0.0.14"
|
||||||
"@radix-ui/react-roving-focus" "0.0.13"
|
"@radix-ui/react-roving-focus" "0.0.14"
|
||||||
"@radix-ui/react-slot" "0.0.10"
|
"@radix-ui/react-slot" "0.0.11"
|
||||||
"@radix-ui/react-use-callback-ref" "0.0.5"
|
"@radix-ui/react-use-callback-ref" "0.0.5"
|
||||||
|
"@radix-ui/react-use-direction" "0.0.1"
|
||||||
aria-hidden "^1.1.1"
|
aria-hidden "^1.1.1"
|
||||||
react-remove-scroll "^2.4.0"
|
react-remove-scroll "^2.4.0"
|
||||||
|
|
||||||
"@radix-ui/react-polymorphic@0.0.11":
|
"@radix-ui/react-polymorphic@0.0.12":
|
||||||
version "0.0.11"
|
version "0.0.12"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-polymorphic/-/react-polymorphic-0.0.11.tgz#23f26b14e67a0e57cd981c37618d603b952af80d"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-polymorphic/-/react-polymorphic-0.0.12.tgz#bf4ae516669b68e059549538104d97322f7c876b"
|
||||||
integrity sha512-CztM4962esOx3i1ls6GuY9RBYIY2Df1Bmp5emHRTxZi8owyCZwZYPctYaDuMO0qIGikPiKD8HBion/m7VWUyEA==
|
integrity sha512-/GYNMicBnGzjD1d2fCAuzql1VeFrp8mqM3xfzT1kxhnV85TKdURO45jBfMgqo17XNXoNhWIAProUsCO4qFAAIg==
|
||||||
dependencies:
|
|
||||||
"@babel/runtime" "^7.13.10"
|
|
||||||
|
|
||||||
"@radix-ui/react-popper@0.0.16":
|
"@radix-ui/react-popper@0.0.17":
|
||||||
version "0.0.16"
|
version "0.0.17"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-0.0.16.tgz#62cccb7d920dc89e076bbdc3421db8c84078f428"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-0.0.17.tgz#a73486e19a628cb3fecaf3fb6eecf6e2cab9d0be"
|
||||||
integrity sha512-njciQ/eIKaDF9h+27Pwi1H6NliQbzTgaHOsT0w/Lxx4vfMe8zcHtiEigYVGErNR4zAYlbW72KzLjtngtNnaorg==
|
integrity sha512-2Lk5AjlCcN9B6fQwQ+y1Zb93f1LfyCK6u0oOAUsPtwbnYEHCdC6wuCFBOZ7AonpjHbrHbY6QDrLGcC43TY6ihw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.13.10"
|
"@babel/runtime" "^7.13.10"
|
||||||
"@radix-ui/popper" "0.0.10"
|
"@radix-ui/popper" "0.0.10"
|
||||||
"@radix-ui/react-arrow" "0.0.13"
|
"@radix-ui/react-arrow" "0.0.14"
|
||||||
"@radix-ui/react-compose-refs" "0.0.5"
|
"@radix-ui/react-compose-refs" "0.0.5"
|
||||||
"@radix-ui/react-context" "0.0.5"
|
"@radix-ui/react-context" "0.0.5"
|
||||||
"@radix-ui/react-polymorphic" "0.0.11"
|
"@radix-ui/react-polymorphic" "0.0.12"
|
||||||
"@radix-ui/react-primitive" "0.0.13"
|
"@radix-ui/react-primitive" "0.0.14"
|
||||||
"@radix-ui/react-use-rect" "0.0.7"
|
"@radix-ui/react-use-rect" "0.0.7"
|
||||||
"@radix-ui/react-use-size" "0.0.6"
|
"@radix-ui/react-use-size" "0.0.6"
|
||||||
"@radix-ui/rect" "0.0.5"
|
"@radix-ui/rect" "0.0.5"
|
||||||
|
|
||||||
"@radix-ui/react-portal@0.0.13":
|
"@radix-ui/react-portal@0.0.14":
|
||||||
version "0.0.13"
|
version "0.0.14"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-0.0.13.tgz#8e60e9ee9b1594f98ee4a8f24a9851ef7ef2ad31"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-0.0.14.tgz#31513d8777cf5e50a3a30ebc9deb34821e890e9e"
|
||||||
integrity sha512-Mw5hrxds2T9HTGwdDbvlKGvTUfcuKhPtqxLnizOrM685e80j+pfjAAfMSXSyfmra9KFQvk3XxmUP0d4U6+kzMg==
|
integrity sha512-Wi9arVwVenonjZIX6znCBYaasua03Tb+UtrBZShepJkLGtbGxDlzExijiGIaIRNetl46Oc2pw0F6Y6HffDnUww==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.13.10"
|
"@babel/runtime" "^7.13.10"
|
||||||
"@radix-ui/react-polymorphic" "0.0.11"
|
"@radix-ui/react-polymorphic" "0.0.12"
|
||||||
"@radix-ui/react-primitive" "0.0.13"
|
"@radix-ui/react-primitive" "0.0.14"
|
||||||
"@radix-ui/react-use-layout-effect" "0.0.5"
|
"@radix-ui/react-use-layout-effect" "0.0.5"
|
||||||
|
|
||||||
"@radix-ui/react-presence@0.0.14":
|
"@radix-ui/react-presence@0.0.14":
|
||||||
|
@ -1782,79 +1791,80 @@
|
||||||
"@babel/runtime" "^7.13.10"
|
"@babel/runtime" "^7.13.10"
|
||||||
"@radix-ui/react-compose-refs" "0.0.5"
|
"@radix-ui/react-compose-refs" "0.0.5"
|
||||||
|
|
||||||
"@radix-ui/react-primitive@0.0.13":
|
"@radix-ui/react-primitive@0.0.14":
|
||||||
version "0.0.13"
|
version "0.0.14"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-0.0.13.tgz#79c606c3e7da9c377b77a7b3f4ec879b45de47a2"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-0.0.14.tgz#752a967cb05d4c5643634fe20274e7dc905d1cce"
|
||||||
integrity sha512-6xxWzw67t7ZuN9Ikn9NNQdb/HSF7dNHPN3kPcgjiVgTEZa3tKk1xjSxGjjQztE61g9GrnTLpu7mBjmEuZDI/lA==
|
integrity sha512-FYOWGCrxFpLdB534aWTwMK4Pjg8cxFb+745qWhPfI+cYi+aYUddJQD3ilRHHXxCBD72ve7/PufqeB7Y/QlKqgg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.13.10"
|
"@babel/runtime" "^7.13.10"
|
||||||
"@radix-ui/react-polymorphic" "0.0.11"
|
"@radix-ui/react-polymorphic" "0.0.12"
|
||||||
|
|
||||||
"@radix-ui/react-radio-group@^0.0.16":
|
"@radix-ui/react-radio-group@^0.0.17":
|
||||||
version "0.0.16"
|
version "0.0.17"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-radio-group/-/react-radio-group-0.0.16.tgz#10fc6e5c3102599cf422e9f6f8d2766088e602a1"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-radio-group/-/react-radio-group-0.0.17.tgz#29ae3d361fc6823cd082d29b7d96efd370dfdf2a"
|
||||||
integrity sha512-vOtgflNWcauSul+EvnPCxATdmPw7fb1cuqBJX07yJdjbrw1Iv5v/+d79fNyIwPR+KrkhP+uCMIBfF0gvo6K7ZQ==
|
integrity sha512-Xvibkj4jGxV9AipZyhFU8xXI5pFPSx/7OGxkRMwHs6U1a28vWppVq++hBhybxCNPO1ZragC2hzl7xdtkz+sugg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.13.10"
|
"@babel/runtime" "^7.13.10"
|
||||||
"@radix-ui/primitive" "0.0.5"
|
"@radix-ui/primitive" "0.0.5"
|
||||||
"@radix-ui/react-compose-refs" "0.0.5"
|
"@radix-ui/react-compose-refs" "0.0.5"
|
||||||
"@radix-ui/react-context" "0.0.5"
|
"@radix-ui/react-context" "0.0.5"
|
||||||
"@radix-ui/react-label" "0.0.13"
|
"@radix-ui/react-label" "0.0.14"
|
||||||
"@radix-ui/react-polymorphic" "0.0.11"
|
"@radix-ui/react-polymorphic" "0.0.12"
|
||||||
"@radix-ui/react-presence" "0.0.14"
|
"@radix-ui/react-presence" "0.0.14"
|
||||||
"@radix-ui/react-primitive" "0.0.13"
|
"@radix-ui/react-primitive" "0.0.14"
|
||||||
"@radix-ui/react-roving-focus" "0.0.13"
|
"@radix-ui/react-roving-focus" "0.0.14"
|
||||||
"@radix-ui/react-slot" "0.0.10"
|
"@radix-ui/react-slot" "0.0.11"
|
||||||
"@radix-ui/react-use-callback-ref" "0.0.5"
|
"@radix-ui/react-use-callback-ref" "0.0.5"
|
||||||
"@radix-ui/react-use-controllable-state" "0.0.6"
|
"@radix-ui/react-use-controllable-state" "0.0.6"
|
||||||
|
"@radix-ui/react-use-previous" "0.0.5"
|
||||||
|
"@radix-ui/react-use-size" "0.0.6"
|
||||||
|
|
||||||
"@radix-ui/react-roving-focus@0.0.13":
|
"@radix-ui/react-roving-focus@0.0.14":
|
||||||
version "0.0.13"
|
version "0.0.14"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-0.0.13.tgz#c72f503832577979c4caa9efcfd59140730c2f80"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-0.0.14.tgz#cedc20ee78227199e4379434489cdd7c00e3edbd"
|
||||||
integrity sha512-jTx3TBMYEdj9SFzRDryO62M9ZK28pYvKKxj/mxWMpMbIjF4q6L+yJmDxtv6glHgCRkwBEyulFVGCjjDg+qxYRA==
|
integrity sha512-MHYdC5kyx/3V8/9DbvMDOCPHP/4blRkaD3EUM4w7VTHUHiZgw6+Dnk1Gc2LoTkcx8Oz413aU+S11vyRdw/1pcA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.13.10"
|
"@babel/runtime" "^7.13.10"
|
||||||
"@radix-ui/primitive" "0.0.5"
|
"@radix-ui/primitive" "0.0.5"
|
||||||
"@radix-ui/react-collection" "0.0.12"
|
"@radix-ui/react-collection" "0.0.13"
|
||||||
"@radix-ui/react-compose-refs" "0.0.5"
|
"@radix-ui/react-compose-refs" "0.0.5"
|
||||||
"@radix-ui/react-context" "0.0.5"
|
"@radix-ui/react-context" "0.0.5"
|
||||||
"@radix-ui/react-id" "0.0.6"
|
"@radix-ui/react-id" "0.0.6"
|
||||||
"@radix-ui/react-polymorphic" "0.0.11"
|
"@radix-ui/react-polymorphic" "0.0.12"
|
||||||
"@radix-ui/react-primitive" "0.0.13"
|
"@radix-ui/react-primitive" "0.0.14"
|
||||||
"@radix-ui/react-use-callback-ref" "0.0.5"
|
"@radix-ui/react-use-callback-ref" "0.0.5"
|
||||||
"@radix-ui/react-use-controllable-state" "0.0.6"
|
"@radix-ui/react-use-controllable-state" "0.0.6"
|
||||||
|
|
||||||
"@radix-ui/react-slot@0.0.10":
|
"@radix-ui/react-slot@0.0.11":
|
||||||
version "0.0.10"
|
version "0.0.11"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-0.0.10.tgz#27a17cad7064872117aeb68113fa934bc5d34c37"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-0.0.11.tgz#d0121018ae20d86683d527b7011b7f21465dffbd"
|
||||||
integrity sha512-p0jJj6lTz1RV2imavnclk8Gda002ZSDR4/zPJ4EQBhspGnx7Y8l6G59c8lxJrT7c7F46F2eRNjpTTjFqqir6EQ==
|
integrity sha512-H/l58Aqoyi9Jq5Jcny5v6HeMNelOivm2NUdaIvMBJzysbbwWhc78VEmpOpfQ6H+y/Fsx6aLprHbfmxPd/66PxQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.13.10"
|
"@babel/runtime" "^7.13.10"
|
||||||
"@radix-ui/primitive" "0.0.5"
|
|
||||||
"@radix-ui/react-compose-refs" "0.0.5"
|
"@radix-ui/react-compose-refs" "0.0.5"
|
||||||
|
|
||||||
"@radix-ui/react-tooltip@^0.0.18":
|
"@radix-ui/react-tooltip@^0.0.19":
|
||||||
version "0.0.18"
|
version "0.0.19"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-0.0.18.tgz#7594297dbc2acf101ac45fdb414c7bc0ac9426bc"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-0.0.19.tgz#2ee843c7c5612708b31ad52adbefc580b213a421"
|
||||||
integrity sha512-oYRAbbTZJ8zEokrrk5pe7QbzF+ZnzMby8mBplrkColi0ntToJ7RKzPgUs1OOFvmg/Nld0Iy2FufrTNlyyEI3kQ==
|
integrity sha512-zDFrGH0nQfRZyk7qD73o22nCbA1QNQoo8cl6RhBLBAFG/b+h1Q5Y4VHu2ZKEz3zZEgrZ8lOKnX8A6dOa9619PQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.13.10"
|
"@babel/runtime" "^7.13.10"
|
||||||
"@radix-ui/primitive" "0.0.5"
|
"@radix-ui/primitive" "0.0.5"
|
||||||
"@radix-ui/react-compose-refs" "0.0.5"
|
"@radix-ui/react-compose-refs" "0.0.5"
|
||||||
"@radix-ui/react-context" "0.0.5"
|
"@radix-ui/react-context" "0.0.5"
|
||||||
"@radix-ui/react-id" "0.0.6"
|
"@radix-ui/react-id" "0.0.6"
|
||||||
"@radix-ui/react-polymorphic" "0.0.11"
|
"@radix-ui/react-polymorphic" "0.0.12"
|
||||||
"@radix-ui/react-popper" "0.0.16"
|
"@radix-ui/react-popper" "0.0.17"
|
||||||
"@radix-ui/react-portal" "0.0.13"
|
"@radix-ui/react-portal" "0.0.14"
|
||||||
"@radix-ui/react-presence" "0.0.14"
|
"@radix-ui/react-presence" "0.0.14"
|
||||||
"@radix-ui/react-primitive" "0.0.13"
|
"@radix-ui/react-primitive" "0.0.14"
|
||||||
"@radix-ui/react-slot" "0.0.10"
|
"@radix-ui/react-slot" "0.0.11"
|
||||||
"@radix-ui/react-use-controllable-state" "0.0.6"
|
"@radix-ui/react-use-controllable-state" "0.0.6"
|
||||||
"@radix-ui/react-use-escape-keydown" "0.0.6"
|
"@radix-ui/react-use-escape-keydown" "0.0.6"
|
||||||
"@radix-ui/react-use-layout-effect" "0.0.5"
|
"@radix-ui/react-use-layout-effect" "0.0.5"
|
||||||
"@radix-ui/react-use-previous" "0.0.5"
|
"@radix-ui/react-use-previous" "0.0.5"
|
||||||
"@radix-ui/react-use-rect" "0.0.7"
|
"@radix-ui/react-use-rect" "0.0.7"
|
||||||
"@radix-ui/react-visually-hidden" "0.0.13"
|
"@radix-ui/react-visually-hidden" "0.0.14"
|
||||||
|
|
||||||
"@radix-ui/react-use-body-pointer-events@0.0.6":
|
"@radix-ui/react-use-body-pointer-events@0.0.6":
|
||||||
version "0.0.6"
|
version "0.0.6"
|
||||||
|
@ -1879,6 +1889,13 @@
|
||||||
"@babel/runtime" "^7.13.10"
|
"@babel/runtime" "^7.13.10"
|
||||||
"@radix-ui/react-use-callback-ref" "0.0.5"
|
"@radix-ui/react-use-callback-ref" "0.0.5"
|
||||||
|
|
||||||
|
"@radix-ui/react-use-direction@0.0.1":
|
||||||
|
version "0.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-direction/-/react-use-direction-0.0.1.tgz#9ac72eb6d9902ed505c8a34048981d94f9433e14"
|
||||||
|
integrity sha512-sU+tkP09uEI1m+YJAR1ZAZLVFY1h/JD+jLSSQt5Wo3b9SYrJA889i2hH1P3DNRyWbbbisweiEQdK3MWILhFCig==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
|
||||||
"@radix-ui/react-use-escape-keydown@0.0.6":
|
"@radix-ui/react-use-escape-keydown@0.0.6":
|
||||||
version "0.0.6"
|
version "0.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-0.0.6.tgz#1ad1c81b99961b7dbe376ef54151ebc8bef627a0"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-0.0.6.tgz#1ad1c81b99961b7dbe376ef54151ebc8bef627a0"
|
||||||
|
@ -1916,14 +1933,14 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.13.10"
|
"@babel/runtime" "^7.13.10"
|
||||||
|
|
||||||
"@radix-ui/react-visually-hidden@0.0.13":
|
"@radix-ui/react-visually-hidden@0.0.14":
|
||||||
version "0.0.13"
|
version "0.0.14"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-0.0.13.tgz#c7f69097eb7d796dcd9117cdd228d87991c08baf"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-0.0.14.tgz#47b764d295275572aa290dabc81c39be3091663b"
|
||||||
integrity sha512-8VNuE4/3PnyrLv1je56fxaa5qka0Nb6/FlyQEDF2HCPpxVOWR4sxRfSBe8cjy+Me+pJN9ZoKBIuoFCVRk54xJA==
|
integrity sha512-fCz6Q24EWfU5uX4JGXFoO2I5H5otGzyyrxyKeOKYpkWiTGY93CO9C4oN7rXiRNCxkm68bgynAgIG40TjOqy0Lw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.13.10"
|
"@babel/runtime" "^7.13.10"
|
||||||
"@radix-ui/react-polymorphic" "0.0.11"
|
"@radix-ui/react-polymorphic" "0.0.12"
|
||||||
"@radix-ui/react-primitive" "0.0.13"
|
"@radix-ui/react-primitive" "0.0.14"
|
||||||
|
|
||||||
"@radix-ui/rect@0.0.5":
|
"@radix-ui/rect@0.0.5":
|
||||||
version "0.0.5"
|
version "0.0.5"
|
||||||
|
@ -2034,17 +2051,10 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@state-designer/core" latest
|
"@state-designer/core" latest
|
||||||
|
|
||||||
"@stitches/core@^0.1.9":
|
"@stitches/react@^0.2.1":
|
||||||
version "0.1.9"
|
version "0.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/@stitches/core/-/core-0.1.9.tgz#6cc42ae6052f6900f9add164232449ff8d62c4ba"
|
resolved "https://registry.yarnpkg.com/@stitches/react/-/react-0.2.1.tgz#c63a0a337b849b302da2342cb5d7aeb6ea257913"
|
||||||
integrity sha512-70gVb0uhgV6BOJmloirWEGZmofk/Hzr5yWlub1rtm4CvimD3Xl5xr9fVkjcTu+REC6ILCJCnlEP4WmilTfXNOA==
|
integrity sha512-LPCOUXsadQM4JrtHQEsOJ+msyss0KySO9jL8L2SUVi9Vi6BfbyEi1mVGaqgvIQRZQfWsM0Se/3AXcl9rUQi5Nw==
|
||||||
|
|
||||||
"@stitches/react@^0.1.9":
|
|
||||||
version "0.1.9"
|
|
||||||
resolved "https://registry.yarnpkg.com/@stitches/react/-/react-0.1.9.tgz#b16e05c9ee7e34369bb3b051b92a997ab379863f"
|
|
||||||
integrity sha512-yK4ZAdkuOiOum+bfPYlDNS9UI/Tm9JYsYNMUWLB3uAy+7tvSYfB4cVYkXwFuiKxis03woATTZTe6IlTb+LXm+A==
|
|
||||||
dependencies:
|
|
||||||
"@stitches/core" "^0.1.9"
|
|
||||||
|
|
||||||
"@surma/rollup-plugin-off-main-thread@^1.4.1":
|
"@surma/rollup-plugin-off-main-thread@^1.4.1":
|
||||||
version "1.4.2"
|
version "1.4.2"
|
||||||
|
@ -7134,10 +7144,10 @@ pend@~1.2.0:
|
||||||
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
||||||
integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA=
|
integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA=
|
||||||
|
|
||||||
perfect-freehand@^0.4.8:
|
perfect-freehand@^0.4.9:
|
||||||
version "0.4.8"
|
version "0.4.9"
|
||||||
resolved "https://registry.yarnpkg.com/perfect-freehand/-/perfect-freehand-0.4.8.tgz#0054995322fdd9939c0c38c260d96a9d0f22eb4f"
|
resolved "https://registry.yarnpkg.com/perfect-freehand/-/perfect-freehand-0.4.9.tgz#09e9f5a1057da7eda335367d5f1f2c6fbd369e3e"
|
||||||
integrity sha512-zU0hvTh0ctjb/h5+nwFhb+/5ZnqS8Z16mn7lY2tYy3NwTkjrEKZ8CJvQ2phlOV5kk+BnCDFGVz7tkKrl9+Mr9w==
|
integrity sha512-mNf9Yd2dxWV3kZs2PPNWjLOt8Yh31+PIu6/zst7I8YJOicYBnRofzXl9us5gxqK4ZfHKO5O7PjttHF0tuHfo0g==
|
||||||
|
|
||||||
performance-now@^2.1.0:
|
performance-now@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue