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