diff --git a/__tests__/code.test.ts b/__tests__/code.test.ts index 4c6602035..58c0deeb5 100644 --- a/__tests__/code.test.ts +++ b/__tests__/code.test.ts @@ -1,7 +1,7 @@ import state from 'state' import { generateFromCode } from 'state/code/generate' -import { getShape, getShapes } from 'utils' import * as json from './__mocks__/document.json' +import tld from 'utils/tld' jest.useRealTimers() @@ -19,7 +19,7 @@ describe('selection', () => { }) it('saves changes to code', () => { - expect(getShapes(state.data).length).toBe(0) + expect(tld.getShapes(state.data).length).toBe(0) const code = `// hello world!` @@ -49,7 +49,7 @@ describe('selection', () => { state.send('GENERATED_FROM_CODE', { controls, shapes }) - expect(getShapes(state.data)).toMatchSnapshot( + expect(tld.getShapes(state.data)).toMatchSnapshot( 'generated rectangle from code' ) }) @@ -106,7 +106,7 @@ describe('selection', () => { 'data in state after changing control' ) - expect(getShape(state.data, 'test-rectangle')).toMatchSnapshot( + expect(tld.getShape(state.data, 'test-rectangle')).toMatchSnapshot( 'rectangle in state after changing code control' ) }) @@ -116,7 +116,7 @@ describe('selection', () => { it('does not saves changes to code when readonly', () => { state.send('CLEARED_PAGE') - expect(getShapes(state.data).length).toBe(0) + expect(tld.getShapes(state.data).length).toBe(0) const code = `// hello world!` @@ -190,7 +190,7 @@ describe('selection', () => { state.send('GENERATED_FROM_CODE', { controls, shapes }) - expect(getShapes(state.data)).toMatchSnapshot( + expect(tld.getShapes(state.data)).toMatchSnapshot( 'generated rectangle from code' ) }) @@ -220,7 +220,9 @@ describe('selection', () => { state.send('GENERATED_FROM_CODE', { controls, shapes }) - expect(getShapes(state.data)).toMatchSnapshot('generated ellipse from code') + expect(tld.getShapes(state.data)).toMatchSnapshot( + 'generated ellipse from code' + ) }) it('generates a draw shape', async () => { @@ -242,7 +244,9 @@ describe('selection', () => { state.send('GENERATED_FROM_CODE', { controls, shapes }) - expect(getShapes(state.data)).toMatchSnapshot('generated draw from code') + expect(tld.getShapes(state.data)).toMatchSnapshot( + 'generated draw from code' + ) }) it('generates an arrow shape', async () => { @@ -264,7 +268,9 @@ describe('selection', () => { state.send('GENERATED_FROM_CODE', { controls, shapes }) - expect(getShapes(state.data)).toMatchSnapshot('generated draw from code') + expect(tld.getShapes(state.data)).toMatchSnapshot( + 'generated draw from code' + ) }) it('generates a text shape', async () => { @@ -287,6 +293,8 @@ describe('selection', () => { state.send('GENERATED_FROM_CODE', { controls, shapes }) - expect(getShapes(state.data)).toMatchSnapshot('generated draw from code') + expect(tld.getShapes(state.data)).toMatchSnapshot( + 'generated draw from code' + ) }) }) diff --git a/__tests__/delete.test.ts b/__tests__/delete.test.ts index 9ceb697f6..4503231c5 100644 --- a/__tests__/delete.test.ts +++ b/__tests__/delete.test.ts @@ -69,7 +69,7 @@ describe('deletes and restores grouped shapes', () => { // Should select the group expect(assertShapeProps(group, { type: ShapeType.Group })) - const arrow = getShape(state.data, arrowId) + const arrow = tld.getShape(state.data, arrowId) // The arrow should be have the group as its parent expect(assertShapeProps(arrow, { parentId: group.id })) diff --git a/__tests__/test-utils.ts b/__tests__/test-utils.ts index 87e4ee58e..a928b7aee 100644 --- a/__tests__/test-utils.ts +++ b/__tests__/test-utils.ts @@ -1,5 +1,5 @@ import { Data, Shape, ShapeType } from 'types' -import { getSelectedIds, getSelectedShapes, getShape } from 'utils' +import tld from 'utils/tld' export const rectangleId = '1f6c251c-e12e-40b4-8dd2-c1847d80b72f' export const arrowId = '5ca167d7-54de-47c9-aa8f-86affa25e44d' @@ -40,7 +40,7 @@ export function idsAreSelected( ids: string[], strict = true ): boolean { - const selectedIds = getSelectedIds(data) + const selectedIds = tld.getSelectedIds(data) return ( (strict ? selectedIds.size === ids.length : true) && ids.every((id) => selectedIds.has(id)) @@ -52,11 +52,11 @@ export function hasParent( childId: string, parentId: string ): boolean { - return getShape(data, childId).parentId === parentId + return tld.getShape(data, childId).parentId === parentId } export function getOnlySelectedShape(data: Data): Shape { - const selectedShapes = getSelectedShapes(data) + const selectedShapes = tld.getSelectedShapes(data) return selectedShapes.length === 1 ? selectedShapes[0] : undefined } @@ -65,7 +65,7 @@ export function assertShapeType( shapeId: string, type: ShapeType ): boolean { - const shape = getShape(data, shapeId) + const shape = tld.getShape(data, shapeId) if (shape.type !== type) { throw new TypeError( `expected shape ${shapeId} to be of type ${type}, found ${shape?.type} instead` diff --git a/components/canvas/bounds/bounding-box.tsx b/components/canvas/bounds/bounding-box.tsx index f56b4b2a6..60e45401c 100644 --- a/components/canvas/bounds/bounding-box.tsx +++ b/components/canvas/bounds/bounding-box.tsx @@ -1,13 +1,8 @@ import * as React from 'react' import { Edge, Corner } from 'types' import { useSelector } from 'state' -import { - getBoundsCenter, - getCurrentCamera, - getPage, - getSelectedShapes, - isMobile, -} from 'utils' +import { getBoundsCenter, isMobile } from 'utils' +import tld from 'utils/tld' import CenterHandle from './center-handle' import CornerHandle from './corner-handle' import EdgeHandle from './edge-handle' @@ -18,23 +13,23 @@ export default function Bounds(): JSX.Element { const isSelecting = useSelector((s) => s.isIn('selecting')) - const zoom = useSelector((s) => getCurrentCamera(s.data).zoom) + const zoom = useSelector((s) => tld.getCurrentCamera(s.data).zoom) const bounds = useSelector((s) => s.values.selectedBounds) const rotation = useSelector((s) => s.values.selectedIds.length === 1 - ? getSelectedShapes(s.data)[0].rotation + ? tld.getSelectedShapes(s.data)[0].rotation : 0 ) const isAllLocked = useSelector((s) => { - const page = getPage(s.data) + const page = tld.getPage(s.data) return s.values.selectedIds.every((id) => page.shapes[id]?.isLocked) }) const isSingleHandles = useSelector((s) => { - const page = getPage(s.data) + const page = tld.getPage(s.data) return ( s.values.selectedIds.length === 1 && page.shapes[s.values.selectedIds[0]]?.handles !== undefined diff --git a/components/canvas/bounds/bounds-bg.tsx b/components/canvas/bounds/bounds-bg.tsx index 96fd1fcd6..2e6b43698 100644 --- a/components/canvas/bounds/bounds-bg.tsx +++ b/components/canvas/bounds/bounds-bg.tsx @@ -2,7 +2,7 @@ import { useRef } from 'react' import state, { useSelector } from 'state' import inputs from 'state/inputs' import styled from 'styles' -import { getPage } from 'utils' +import tld from 'utils/tld' function handlePointerDown(e: React.PointerEvent) { if (!inputs.canAccept(e.pointerId)) return @@ -36,7 +36,7 @@ export default function BoundsBg(): JSX.Element { if (selectedIds.length === 1) { const selected = selectedIds[0] - const page = getPage(s.data) + const page = tld.getPage(s.data) return page.shapes[selected]?.rotation } else { @@ -48,7 +48,7 @@ export default function BoundsBg(): JSX.Element { const selectedIds = s.values.selectedIds if (selectedIds.length === 1) { - const page = getPage(s.data) + const page = tld.getPage(s.data) const selected = selectedIds[0] return ( diff --git a/components/canvas/bounds/handles.tsx b/components/canvas/bounds/handles.tsx index de105e8e9..ce810cfea 100644 --- a/components/canvas/bounds/handles.tsx +++ b/components/canvas/bounds/handles.tsx @@ -3,14 +3,14 @@ import { getShapeUtils } from 'state/shape-utils' import { useRef } from 'react' import { useSelector } from 'state' import styled from 'styles' -import { getPage } from 'utils' +import tld from 'utils/tld' import vec from 'utils/vec' export default function Handles(): JSX.Element { const shape = useSelector( (s) => s.values.selectedIds.length === 1 && - getPage(s.data).shapes[s.values.selectedIds[0]] + tld.getPage(s.data).shapes[s.values.selectedIds[0]] ) const isSelecting = useSelector((s) => diff --git a/components/canvas/context-menu/context-menu.tsx b/components/canvas/context-menu/context-menu.tsx index 012812cbf..3e8b11829 100644 --- a/components/canvas/context-menu/context-menu.tsx +++ b/components/canvas/context-menu/context-menu.tsx @@ -4,8 +4,9 @@ import { IconWrapper, IconButton as _IconButton, RowButton, + breakpoints, } from 'components/shared' -import { commandKey, deepCompareArrays, getShape, isMobile } from 'utils' +import { commandKey, deepCompareArrays, isMobile } from 'utils' import state, { useSelector } from 'state' import { AlignType, @@ -14,6 +15,7 @@ import { ShapeType, StretchType, } from 'types' +import tld from 'utils/tld' import React, { useRef } from 'react' import { ChevronRightIcon, @@ -82,7 +84,9 @@ export default function ContextMenu({ const rContent = useRef(null) const hasGroupSelected = useSelector((s) => - selectedShapeIds.some((id) => getShape(s.data, id).type === ShapeType.Group) + selectedShapeIds.some( + (id) => tld.getShape(s.data, id).type === ShapeType.Group + ) ) const hasTwoOrMore = selectedShapeIds.length > 1 @@ -300,7 +304,7 @@ function Button({ <_ContextMenu.Item as={RowButton} disabled={disabled} - bp={{ '@initial': 'mobile', '@sm': 'small' }} + bp={breakpoints} onSelect={onSelect} > {children} @@ -320,7 +324,7 @@ function IconButton({ return ( <_ContextMenu.Item as={_IconButton} - bp={{ '@initial': 'mobile', '@sm': 'small' }} + bp={breakpoints} disabled={disabled} onSelect={onSelect} > @@ -338,10 +342,7 @@ function SubMenu({ }) { return ( <_ContextMenu.Root dir="ltr"> - <_ContextMenu.TriggerItem - as={RowButton} - bp={{ '@initial': 'mobile', '@sm': 'small' }} - > + <_ContextMenu.TriggerItem as={RowButton} bp={breakpoints}> {label} @@ -363,10 +364,7 @@ function AlignDistributeSubMenu({ }) { return ( <_ContextMenu.Root dir="ltr"> - <_ContextMenu.TriggerItem - as={RowButton} - bp={{ '@initial': 'mobile', '@sm': 'small' }} - > + <_ContextMenu.TriggerItem as={RowButton} bp={breakpoints}> Align / Distribute @@ -447,10 +445,7 @@ function MoveToPageMenu() { return ( <_ContextMenu.Root dir="ltr"> - <_ContextMenu.TriggerItem - as={RowButton} - bp={{ '@initial': 'mobile', '@sm': 'small' }} - > + <_ContextMenu.TriggerItem as={RowButton} bp={breakpoints}> Move To Page diff --git a/components/canvas/defs.tsx b/components/canvas/defs.tsx index 212a61803..b1ea98329 100644 --- a/components/canvas/defs.tsx +++ b/components/canvas/defs.tsx @@ -2,7 +2,7 @@ import { getShapeStyle } from 'state/shape-styles' import { getShapeUtils } from 'state/shape-utils' import React from 'react' import { useSelector } from 'state' -import { getCurrentCamera } from 'utils' +import tld from 'utils/tld' import { DotCircle, Handle } from './misc' import useShapeDef from 'hooks/useShape' import useShapesToRender from 'hooks/useShapesToRender' @@ -40,7 +40,7 @@ function Def({ id }: { id: string }) { } function ExpandDef() { - const zoom = useSelector((s) => getCurrentCamera(s.data).zoom) + const zoom = useSelector((s) => tld.getCurrentCamera(s.data).zoom) return ( diff --git a/components/canvas/hovered-shape.tsx b/components/canvas/hovered-shape.tsx index bc7009a65..b3e60dfe3 100644 --- a/components/canvas/hovered-shape.tsx +++ b/components/canvas/hovered-shape.tsx @@ -1,5 +1,5 @@ import { memo } from 'react' -import { getShape } from 'utils' +import tld from 'utils/tld' import { getShapeUtils } from 'state/shape-utils' import vec from 'utils/vec' import styled from 'styles' @@ -8,10 +8,10 @@ import { getShapeStyle } from 'state/shape-styles' function HoveredShape({ id }: { id: string }) { const transform = useSelector((s) => { - const shape = getShape(s.data, id) + const shape = tld.getShape(s.data, id) const center = getShapeUtils(shape).getCenter(shape) const rotation = shape.rotation * (180 / Math.PI) - const parentPoint = getShape(s.data, shape.parentId)?.point || [0, 0] + const parentPoint = tld.getShape(s.data, shape.parentId)?.point || [0, 0] return ` translate(${vec.neg(parentPoint)}) @@ -21,7 +21,7 @@ function HoveredShape({ id }: { id: string }) { }) const strokeWidth = useSelector((s) => { - const shape = getShape(s.data, id) + const shape = tld.getShape(s.data, id) const style = getShapeStyle(shape.style) return +style.strokeWidth }) diff --git a/components/canvas/selected.tsx b/components/canvas/selected.tsx index e7567932e..8fee635e4 100644 --- a/components/canvas/selected.tsx +++ b/components/canvas/selected.tsx @@ -1,6 +1,7 @@ import styled from 'styles' import { useSelector } from 'state' -import { deepCompareArrays, getPage } from 'utils' +import tld from 'utils/tld' +import { deepCompareArrays } from 'utils' import { getShapeUtils } from 'state/shape-utils' import { memo } from 'react' @@ -26,7 +27,7 @@ export default function Selected(): JSX.Element { export const ShapeOutline = memo(function ShapeOutline({ id }: { id: string }) { // const rIndicator = useRef(null) - const shape = useSelector((s) => getPage(s.data).shapes[id]) + const shape = useSelector((s) => tld.getShape(s.data, id)) // const events = useShapeEvents(id, shape?.type === ShapeType.Group, rIndicator) diff --git a/components/canvas/shape.tsx b/components/canvas/shape.tsx index 16274d4a4..333c54104 100644 --- a/components/canvas/shape.tsx +++ b/components/canvas/shape.tsx @@ -2,7 +2,8 @@ import React, { useRef, memo, useEffect, useState } from 'react' import state, { useSelector } from 'state' import styled from 'styles' import { getShapeUtils } from 'state/shape-utils' -import { deepCompareArrays, getPage, getShape } from 'utils' +import { deepCompareArrays } from 'utils' +import tld from 'utils/tld' import useShapeEvents from 'hooks/useShapeEvents' import vec from 'utils/vec' import { getShapeStyle } from 'state/shape-styles' @@ -17,31 +18,31 @@ function Shape({ id, isSelecting }: ShapeProps): JSX.Element { const rGroup = useRef(null) const isHidden = useSelector((s) => { - const shape = getShape(s.data, id) + const shape = tld.getShape(s.data, id) return shape?.isHidden || false }) const children = useSelector((s) => { - const shape = getShape(s.data, id) + const shape = tld.getShape(s.data, id) return shape?.children || [] }, deepCompareArrays) const strokeWidth = useSelector((s) => { - const shape = getShape(s.data, id) + const shape = tld.getShape(s.data, id) const style = getShapeStyle(shape?.style) return +style.strokeWidth }) const shapeUtils = useSelector((s) => { - const shape = getShape(s.data, id) + const shape = tld.getShape(s.data, id) return getShapeUtils(shape) }) const transform = useSelector((s) => { - const shape = getShape(s.data, id) + const shape = tld.getShape(s.data, id) const center = getShapeUtils(shape).getCenter(shape) const rotation = shape.rotation * (180 / Math.PI) - const parentPoint = getShape(s.data, shape.parentId)?.point || [0, 0] + const parentPoint = tld.getShape(s.data, shape.parentId)?.point || [0, 0] return ` translate(${vec.neg(parentPoint)}) @@ -121,7 +122,7 @@ const ForeignObjectHover = memo(function ForeignObjectHover({ id: string }) { const size = useSelector((s) => { - const shape = getPage(s.data).shapes[id] + const shape = tld.getPage(s.data).shapes[id] const bounds = getShapeUtils(shape).getBounds(shape) return [bounds.width, bounds.height] @@ -202,7 +203,7 @@ function useMissingShapeTest(id: string) { useEffect(() => { return state.onUpdate((s) => { - if (isShape && !getShape(s.data, id)) { + if (isShape && !tld.getShape(s.data, id)) { setIsShape(false) } }) diff --git a/components/style-panel/shapes-functions.tsx b/components/style-panel/shapes-functions.tsx index dfadc9392..ae28f049b 100644 --- a/components/style-panel/shapes-functions.tsx +++ b/components/style-panel/shapes-functions.tsx @@ -1,11 +1,11 @@ +import tld from 'utils/tld' +import state, { useSelector } from 'state' import { IconButton, breakpoints } from 'components/shared' import { memo } from 'react' import styled from 'styles' import { MoveType } from 'types' import { Trash2 } from 'react-feather' -import state, { useSelector } from 'state' import Tooltip from 'components/tooltip' - import { ArrowDownIcon, ArrowUpIcon, @@ -20,7 +20,6 @@ import { PinTopIcon, RotateCounterClockwiseIcon, } from '@radix-ui/react-icons' -import { getPage, getSelectedIds } from 'utils' function handleRotateCcw() { state.send('ROTATED_CCW') @@ -64,24 +63,24 @@ function handleDelete() { function ShapesFunctions() { const isAllLocked = useSelector((s) => { - const page = getPage(s.data) + const page = tld.getPage(s.data) return s.values.selectedIds.every((id) => page.shapes[id].isLocked) }) const isAllAspectLocked = useSelector((s) => { - const page = getPage(s.data) + const page = tld.getPage(s.data) return s.values.selectedIds.every( (id) => page.shapes[id].isAspectRatioLocked ) }) const isAllHidden = useSelector((s) => { - const page = getPage(s.data) + const page = tld.getPage(s.data) return s.values.selectedIds.every((id) => page.shapes[id].isHidden) }) const hasSelection = useSelector((s) => { - return getSelectedIds(s.data).size > 0 + return tld.getSelectedIds(s.data).size > 0 }) return ( diff --git a/components/tools-panel/zoom.tsx b/components/tools-panel/zoom.tsx index 97929ae0e..94ef58531 100644 --- a/components/tools-panel/zoom.tsx +++ b/components/tools-panel/zoom.tsx @@ -2,8 +2,8 @@ import { ZoomInIcon, ZoomOutIcon } from '@radix-ui/react-icons' import { IconButton } from 'components/shared' import state, { useSelector } from 'state' import styled from 'styles' -import { getCurrentCamera } from 'utils' import Tooltip from '../tooltip' +import tld from 'utils/tld' const zoomIn = () => state.send('ZOOMED_IN') const zoomOut = () => state.send('ZOOMED_OUT') @@ -31,7 +31,7 @@ export default function Zoom(): JSX.Element { } function ZoomCounter() { - const zoom = useSelector((s) => getCurrentCamera(s.data).zoom) + const zoom = useSelector((s) => tld.getCurrentCamera(s.data).zoom) return ( ) { useEffect(() => { - let prev = getCurrentCamera(state.data) + let prev = tld.getCurrentCamera(state.data) return state.onUpdate(() => { const g = ref.current if (!g) return - const { point, zoom } = getCurrentCamera(state.data) + const { point, zoom } = tld.getCurrentCamera(state.data) if (point !== prev.point || zoom !== prev.zoom) { g.setAttribute( @@ -27,7 +27,7 @@ export default function useCamera(ref: React.MutableRefObject) { storage.savePageState(state.data) - prev = getCurrentCamera(state.data) + prev = tld.getCurrentCamera(state.data) } }) }, [state]) diff --git a/hooks/usePageShapes.ts b/hooks/usePageShapes.ts index e2f0912f3..28f987e7b 100644 --- a/hooks/usePageShapes.ts +++ b/hooks/usePageShapes.ts @@ -7,9 +7,8 @@ import { boundsContain, debounce, deepCompareArrays, - getPageState, - getViewport, } from 'utils' +import tld from 'utils/tld' const viewportCache = new WeakMap() @@ -27,10 +26,10 @@ export default function usePageShapes(): string[] { // Get the shapes that fit into the current window const visiblePageShapeIds = useSelector((s) => { - const pageState = getPageState(s.data) + const pageState = tld.getPageState(s.data) if (!viewportCache.has(pageState)) { - const viewport = getViewport(s.data) + const viewport = tld.getViewport(s.data) viewportCache.set(pageState, viewport) } diff --git a/hooks/useShape.ts b/hooks/useShape.ts index 16f9768b5..5a764a6df 100644 --- a/hooks/useShape.ts +++ b/hooks/useShape.ts @@ -1,11 +1,11 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import { useSelector } from 'state' import { getShapeUtils } from 'state/shape-utils' -import { getShape } from 'utils' +import tld from 'utils/tld' export default function useShapeDef(id: string) { return useSelector( - (s) => getShape(s.data, id), + (s) => tld.getShape(s.data, id), (prev, next) => { const shouldSkip = !( prev && diff --git a/hooks/useShapesToRender.ts b/hooks/useShapesToRender.ts index 3dbcff5d5..164d4c6a3 100644 --- a/hooks/useShapesToRender.ts +++ b/hooks/useShapesToRender.ts @@ -1,11 +1,12 @@ import { useSelector } from 'state' import { getShapeUtils } from 'state/shape-utils' -import { deepCompareArrays, getPage } from 'utils' +import { deepCompareArrays } from 'utils' +import tld from 'utils/tld' export default function useShapesToRender(): string[] { return useSelector( (s) => - Object.values(getPage(s.data).shapes) + Object.values(tld.getPage(s.data).shapes) .filter((shape) => shape && !getShapeUtils(shape).isForeignObject) .map((shape) => shape.id), deepCompareArrays diff --git a/hooks/useZoomEvents.ts b/hooks/useZoomEvents.ts index d32da6c6f..3631d06c5 100644 --- a/hooks/useZoomEvents.ts +++ b/hooks/useZoomEvents.ts @@ -1,5 +1,5 @@ -/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import { useRef } from 'react' - +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +import { useRef } from 'react' import state from 'state' import inputs from 'state/inputs' import vec from 'utils/vec' diff --git a/package.json b/package.json index 8c1455aef..3e5f24e1b 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "@sentry/webpack-plugin": "^1.15.1", "@state-designer/react": "^1.7.32", "@stitches/react": "^0.2.2", + "@supabase/realtime-js": "^1.0.9", "@types/uuid": "^8.3.0", "browser-fs-access": "^0.17.3", "framer-motion": "^4.1.17", diff --git a/state/clipboard.ts b/state/clipboard.ts index c162bc71e..6a137f86c 100644 --- a/state/clipboard.ts +++ b/state/clipboard.ts @@ -1,6 +1,7 @@ import { getShapeUtils } from './shape-utils' import { Data, Shape } from 'types' -import { getCommonBounds, getSelectedShapes } from 'utils' +import { getCommonBounds } from 'utils' +import tld from 'utils/tld' import state from './state' class Clipboard { @@ -47,7 +48,7 @@ class Clipboard { } copySelectionToSvg(data: Data) { - const shapes = getSelectedShapes(data) + const shapes = tld.getSelectedShapes(data) const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg') diff --git a/state/code/generate.ts b/state/code/generate.ts index 87409e566..d6cf25171 100644 --- a/state/code/generate.ts +++ b/state/code/generate.ts @@ -27,7 +27,7 @@ import { SizeStyle, CodeError, } from 'types' -import { getPage, getShapes } from 'utils' +import tld from 'utils/tld' import { transform } from 'sucrase' import { getErrorWithLineAndColumn, getFormattedCode } from 'utils/code' @@ -89,7 +89,8 @@ export async function generateFromCode( ) const startingChildIndex = - getShapes(data) + tld + .getShapes(data) .filter((shape) => shape.parentId === data.currentPageId) .sort((a, b) => a.childIndex - b.childIndex)[0]?.childIndex || 1 @@ -98,7 +99,7 @@ export async function generateFromCode( .map((instance, i) => ({ ...instance.shape, isGenerated: true, - parentId: getPage(data).id, + parentId: tld.getPage(data).id, childIndex: startingChildIndex + i, })) @@ -141,7 +142,8 @@ export async function updateFromCode( } const startingChildIndex = - getShapes(data) + tld + .getShapes(data) .filter((shape) => shape.parentId === data.currentPageId) .sort((a, b) => a.childIndex - b.childIndex)[0]?.childIndex || 1 @@ -156,7 +158,7 @@ export async function updateFromCode( .map((instance, i) => ({ ...instance.shape, isGenerated: true, - parentId: getPage(data).id, + parentId: tld.getPage(data).id, childIndex: startingChildIndex + i, })) diff --git a/state/commands/align.ts b/state/commands/align.ts index 4fbcc1f73..e1c3986a0 100644 --- a/state/commands/align.ts +++ b/state/commands/align.ts @@ -1,11 +1,12 @@ import Command from './command' import history from '../history' import { AlignType, Data } from 'types' -import { getCommonBounds, getPage, getSelectedShapes } from 'utils' +import { getCommonBounds } from 'utils' +import tld from 'utils/tld' import { getShapeUtils } from 'state/shape-utils' export default function alignCommand(data: Data, type: AlignType): void { - const selectedShapes = getSelectedShapes(data) + const selectedShapes = tld.getSelectedShapes(data) const entries = selectedShapes.map( (shape) => [shape.id, getShapeUtils(shape).getBounds(shape)] as const ) @@ -20,7 +21,7 @@ export default function alignCommand(data: Data, type: AlignType): void { name: 'aligned', category: 'canvas', do(data) { - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) switch (type) { case AlignType.Top: { @@ -86,7 +87,7 @@ export default function alignCommand(data: Data, type: AlignType): void { } }, undo(data) { - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) for (const id in boundsForShapes) { const shape = shapes[id] const initialBounds = boundsForShapes[id] diff --git a/state/commands/arrow.ts b/state/commands/arrow.ts index 9669d6cce..f80c9c85f 100644 --- a/state/commands/arrow.ts +++ b/state/commands/arrow.ts @@ -1,7 +1,7 @@ import Command from './command' import history from '../history' import { Data } from 'types' -import { getPage, getSelectedIds } from 'utils' +import tld from 'utils/tld' import { ArrowSnapshot } from 'state/sessions/arrow-session' export default function arrowCommand( @@ -20,11 +20,11 @@ export default function arrowCommand( const { initialShape } = after - const page = getPage(data) + const page = tld.getPage(data) page.shapes[initialShape.id] = initialShape - const selectedIds = getSelectedIds(data) + const selectedIds = tld.getSelectedIds(data) selectedIds.clear() selectedIds.add(initialShape.id) data.hoveredId = undefined @@ -32,11 +32,11 @@ export default function arrowCommand( }, undo(data) { const { initialShape } = before - const shapes = getPage(data).shapes + const shapes = tld.getPage(data).shapes delete shapes[initialShape.id] - const selectedIds = getSelectedIds(data) + const selectedIds = tld.getSelectedIds(data) selectedIds.clear() data.hoveredId = undefined data.pointedId = undefined diff --git a/state/commands/command.ts b/state/commands/command.ts index b0fff3647..5dc83dcaf 100644 --- a/state/commands/command.ts +++ b/state/commands/command.ts @@ -1,5 +1,6 @@ import { Data } from 'types' -import { getSelectedIds, setSelectedIds, setToArray } from 'utils' +import { setToArray } from 'utils' +import tld from 'utils/tld' /* ------------------ Command Class ----------------- */ @@ -84,12 +85,12 @@ export class BaseCommand { export default class Command extends BaseCommand { saveSelectionState = (data: Data): ((next: Data) => void) => { const { currentPageId } = data - const selectedIds = setToArray(getSelectedIds(data)) + const selectedIds = setToArray(tld.getSelectedIds(data)) return (next: Data) => { next.currentPageId = currentPageId next.hoveredId = undefined next.pointedId = undefined - setSelectedIds(next, selectedIds) + tld.setSelectedIds(next, selectedIds) } } } diff --git a/state/commands/delete-page.ts b/state/commands/delete-page.ts index c8b78236d..6f63d0a56 100644 --- a/state/commands/delete-page.ts +++ b/state/commands/delete-page.ts @@ -2,7 +2,8 @@ import Command from './command' import history from '../history' import { Data } from 'types' import storage from 'state/storage' -import { deepClone, getPage, getPageState } from 'utils' +import { deepClone } from 'utils' +import tld from 'utils/tld' export default function deletePage(data: Data, pageId: string): void { const snapshot = getSnapshot(data, pageId) @@ -31,9 +32,9 @@ export default function deletePage(data: Data, pageId: string): void { function getSnapshot(data: Data, pageId: string) { const { currentPageId, document } = data - const page = deepClone(getPage(data)) + const page = deepClone(tld.getPage(data)) - const pageState = deepClone(getPageState(data)) + const pageState = deepClone(tld.getPageState(data)) const isCurrent = data.currentPageId === pageId diff --git a/state/commands/delete-selected.ts b/state/commands/delete-selected.ts index 28ea8283d..3ea692ad0 100644 --- a/state/commands/delete-selected.ts +++ b/state/commands/delete-selected.ts @@ -1,24 +1,19 @@ import Command from './command' import history from '../history' import { Data, Shape } from 'types' -import { - deepClone, - getDocumentBranch, - getPage, - getSelectedShapes, - setSelectedIds, -} from 'utils' +import { deepClone } from 'utils' +import tld from 'utils/tld' import { getShapeUtils } from 'state/shape-utils' export default function deleteSelected(data: Data): void { - const selectedShapes = getSelectedShapes(data) + const selectedShapes = tld.getSelectedShapes(data) const selectedIdsArr = selectedShapes .filter((shape) => !shape.isLocked) .map((shape) => shape.id) const shapeIdsToDelete = selectedIdsArr.flatMap((id) => - getDocumentBranch(data, id) + tld.getDocumentBranch(data, id) ) const remainingIds = selectedShapes @@ -35,16 +30,16 @@ export default function deleteSelected(data: Data): void { manualSelection: true, do(data) { // Update selected ids - setSelectedIds(data, remainingIds) + tld.setSelectedIds(data, remainingIds) // Recursively delete shapes (and maybe their parents too) deletedShapes = deleteShapes(data, shapeIdsToDelete) }, undo(data) { - const page = getPage(data) + const page = tld.getPage(data) // Update selected ids - setSelectedIds(data, selectedIdsArr) + tld.setSelectedIds(data, selectedIdsArr) // Restore deleted shapes deletedShapes.forEach((shape) => (page.shapes[shape.id] = shape)) @@ -76,7 +71,7 @@ function deleteShapes( ): Shape[] { const parentsToDelete: string[] = [] - const page = getPage(data) + const page = tld.getPage(data) const parentIds = new Set(shapeIds.map((id) => page.shapes[id].parentId)) diff --git a/state/commands/direct.ts b/state/commands/direct.ts deleted file mode 100644 index fec7c9bf9..000000000 --- a/state/commands/direct.ts +++ /dev/null @@ -1,37 +0,0 @@ -import Command from './command' -import history from '../history' -import { DirectionSnapshot } from 'state/sessions/direction-session' -import { Data, LineShape, RayShape } from 'types' -import { getPage } from 'utils' - -export default function directCommand( - data: Data, - before: DirectionSnapshot, - after: DirectionSnapshot -): void { - history.execute( - data, - new Command({ - name: 'set_direction', - category: 'canvas', - do(data) { - const { shapes } = getPage(data) - - for (const { id, direction } of after.shapes) { - const shape = shapes[id] as RayShape | LineShape - - shape.direction = direction - } - }, - undo(data) { - const { shapes } = getPage(data) - - for (const { id, direction } of after.shapes) { - const shape = shapes[id] as RayShape | LineShape - - shape.direction = direction - } - }, - }) - ) -} diff --git a/state/commands/distribute.ts b/state/commands/distribute.ts index cfc87895c..491133e5a 100644 --- a/state/commands/distribute.ts +++ b/state/commands/distribute.ts @@ -1,21 +1,17 @@ import Command from './command' import history from '../history' import { Data, DistributeType } from 'types' -import { - getBoundsCenter, - getCommonBounds, - getPage, - getSelectedShapes, -} from 'utils' +import { getBoundsCenter, getCommonBounds } from 'utils' +import tld from 'utils/tld' import { getShapeUtils } from 'state/shape-utils' export default function distributeCommand( data: Data, type: DistributeType ): void { - const selectedShapes = getSelectedShapes(data).filter( - (shape) => !shape.isLocked - ) + const selectedShapes = tld + .getSelectedShapes(data) + .filter((shape) => !shape.isLocked) const entries = selectedShapes.map( (shape) => [shape.id, getShapeUtils(shape).getBounds(shape)] as const @@ -38,7 +34,7 @@ export default function distributeCommand( name: 'distribute_shapes', category: 'canvas', do(data) { - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) const len = entries.length switch (type) { @@ -130,7 +126,7 @@ export default function distributeCommand( } }, undo(data) { - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) for (const id in boundsForShapes) { const shape = shapes[id] const initialBounds = boundsForShapes[id] diff --git a/state/commands/double-point-handle.ts b/state/commands/double-point-handle.ts index d2cb18f4f..d9088a63f 100644 --- a/state/commands/double-point-handle.ts +++ b/state/commands/double-point-handle.ts @@ -2,14 +2,15 @@ import Command from './command' import history from '../history' import { Data, PointerInfo } from 'types' import { getShapeUtils } from 'state/shape-utils' -import { deepClone, getPage, getShape, updateParents } from 'utils' +import { deepClone } from 'utils' +import tld from 'utils/tld' export default function doublePointHandleCommand( data: Data, id: string, payload: PointerInfo ): void { - const initialShape = deepClone(getShape(data, id)) + const initialShape = deepClone(tld.getShape(data, id)) history.execute( data, @@ -17,16 +18,16 @@ export default function doublePointHandleCommand( name: 'double_point_handle', category: 'canvas', do(data) { - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) const shape = shapes[id] getShapeUtils(shape).onDoublePointHandle(shape, payload.target, payload) - updateParents(data, [id]) + tld.updateParents(data, [id]) }, undo(data) { - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) shapes[id] = initialShape - updateParents(data, [id]) + tld.updateParents(data, [id]) }, }) ) diff --git a/state/commands/draw.ts b/state/commands/draw.ts index 7048f6a0a..a2640da3d 100644 --- a/state/commands/draw.ts +++ b/state/commands/draw.ts @@ -1,10 +1,11 @@ import Command from './command' import history from '../history' import { Data, DrawShape } from 'types' -import { deepClone, getPage, getShape, setSelectedIds } from 'utils' +import tld from 'utils/tld' +import { deepClone } from 'utils' export default function drawCommand(data: Data, id: string): void { - const restoreShape = deepClone(getShape(data, id)) as DrawShape + const restoreShape = deepClone(tld.getShape(data, id)) as DrawShape history.execute( data, @@ -14,14 +15,14 @@ export default function drawCommand(data: Data, id: string): void { manualSelection: true, do(data, initial) { if (!initial) { - getPage(data).shapes[id] = restoreShape + tld.getPage(data).shapes[id] = restoreShape } - setSelectedIds(data, []) + tld.setSelectedIds(data, []) }, undo(data) { - setSelectedIds(data, []) - delete getPage(data).shapes[id] + tld.setSelectedIds(data, []) + delete tld.getPage(data).shapes[id] }, }) ) diff --git a/state/commands/duplicate.ts b/state/commands/duplicate.ts index f02837054..3d6f8b11c 100644 --- a/state/commands/duplicate.ts +++ b/state/commands/duplicate.ts @@ -1,23 +1,21 @@ import Command from './command' import history from '../history' import { Data } from 'types' -import { - deepClone, - getCurrentCamera, - getPage, - getSelectedShapes, - setSelectedIds, -} from 'utils' +import { deepClone } from 'utils' +import tld from 'utils/tld' import { uniqueId } from 'utils' import vec from 'utils/vec' export default function duplicateCommand(data: Data): void { - const selectedShapes = getSelectedShapes(data).map(deepClone) + const selectedShapes = tld.getSelectedShapes(data).map(deepClone) const duplicates = selectedShapes.map((shape) => ({ ...shape, id: uniqueId(), - point: vec.add(shape.point, vec.div([16, 16], getCurrentCamera(data).zoom)), + point: vec.add( + shape.point, + vec.div([16, 16], tld.getCurrentCamera(data).zoom) + ), isGenerated: false, })) @@ -28,25 +26,25 @@ export default function duplicateCommand(data: Data): void { category: 'canvas', manualSelection: true, do(data) { - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) for (const duplicate of duplicates) { shapes[duplicate.id] = duplicate } - setSelectedIds( + tld.setSelectedIds( data, duplicates.map((d) => d.id) ) }, undo(data) { - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) for (const duplicate of duplicates) { delete shapes[duplicate.id] } - setSelectedIds( + tld.setSelectedIds( data, selectedShapes.map((d) => d.id) ) diff --git a/state/commands/edit.ts b/state/commands/edit.ts index 9dc8ed108..71c740419 100644 --- a/state/commands/edit.ts +++ b/state/commands/edit.ts @@ -1,7 +1,7 @@ import Command from './command' import history from '../history' import { Data } from 'types' -import { getPage } from 'utils' +import tld from 'utils/tld' import { EditSnapshot } from 'state/sessions/edit-session' import { getShapeUtils } from 'state/shape-utils' @@ -18,7 +18,7 @@ export default function editCommand( do(data) { const { initialShape } = after - const page = getPage(data) + const page = tld.getPage(data) page.shapes[initialShape.id] = initialShape @@ -31,7 +31,7 @@ export default function editCommand( undo(data) { const { initialShape } = before - const page = getPage(data) + const page = tld.getPage(data) page.shapes[initialShape.id] = initialShape }, diff --git a/state/commands/generate.ts b/state/commands/generate.ts index d1981cb2d..7cc787705 100644 --- a/state/commands/generate.ts +++ b/state/commands/generate.ts @@ -1,13 +1,15 @@ import Command from './command' import history from '../history' import { Data, Shape } from 'types' -import { deepClone, getPage, getShapes, setSelectedIds } from 'utils' +import { deepClone } from 'utils' +import tld from 'utils/tld' export default function generateCommand( data: Data, generatedShapes: Shape[] ): void { - const initialShapes = getShapes(data) + const initialShapes = tld + .getShapes(data) .filter((shape) => shape.isGenerated) .map(deepClone) @@ -17,16 +19,16 @@ export default function generateCommand( name: 'generate_shapes', category: 'canvas', do(data) { - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) initialShapes.forEach((shape) => delete shapes[shape.id]) generatedShapes.forEach((shape) => (shapes[shape.id] = shape)) - setSelectedIds(data, []) + tld.setSelectedIds(data, []) }, undo(data) { - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) generatedShapes.forEach((shape) => delete shapes[shape.id]) initialShapes.forEach((shape) => (shapes[shape.id] = shape)) - setSelectedIds(data, []) + tld.setSelectedIds(data, []) }, }) ) diff --git a/state/commands/group.ts b/state/commands/group.ts index e4b97d973..d441c5f25 100644 --- a/state/commands/group.ts +++ b/state/commands/group.ts @@ -1,15 +1,9 @@ import Command from './command' import history from '../history' import { Data, GroupShape, ShapeType } from 'types' -import { - getCommonBounds, - getPage, - getSelectedIds, - getSelectedShapes, - getShape, - setSelectedIds, -} from 'utils' +import { getCommonBounds } from 'utils' import { current } from 'immer' +import tld from 'utils/tld' import { createShape, getShapeUtils } from 'state/shape-utils' import commands from '.' @@ -17,11 +11,11 @@ export default function groupCommand(data: Data): void { const cData = current(data) const { currentPageId } = cData - const oldSelectedIds = getSelectedIds(cData) + const oldSelectedIds = tld.getSelectedIds(cData) - const initialShapes = getSelectedShapes(cData).sort( - (a, b) => a.childIndex - b.childIndex - ) + const initialShapes = tld + .getSelectedShapes(cData) + .sort((a, b) => a.childIndex - b.childIndex) const isAllSameParent = initialShapes.every( (shape, i) => i === 0 || shape.parentId === initialShapes[i - 1].parentId @@ -43,7 +37,7 @@ export default function groupCommand(data: Data): void { newGroupParentId = currentPageId } else { // Are all of the parent's children selected? - const parent = getShape(data, parentId) as GroupShape + const parent = tld.getShape(data, parentId) as GroupShape if (parent.children.length === initialShapes.length) { // !!! Hey! We're not going any further. We need to ungroup those shapes. @@ -82,7 +76,7 @@ export default function groupCommand(data: Data): void { category: 'canvas', manualSelection: true, do(data) { - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) // Create the new group shapes[newGroupShape.id] = newGroupShape @@ -115,10 +109,10 @@ export default function groupCommand(data: Data): void { .setProperty(shape, 'parentId', newGroupShape.id) }) - setSelectedIds(data, [newGroupShape.id]) + tld.setSelectedIds(data, [newGroupShape.id]) }, undo(data) { - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) const group = shapes[newGroupShape.id] @@ -152,7 +146,7 @@ export default function groupCommand(data: Data): void { delete shapes[newGroupShape.id] // Reselect the children of the group - setSelectedIds(data, initialShapeIds) + tld.setSelectedIds(data, initialShapeIds) }, }) ) @@ -163,5 +157,5 @@ function getShapeDepth(data: Data, id: string, depth = 0) { return depth } - return getShapeDepth(data, getShape(data, id).parentId, depth + 1) + return getShapeDepth(data, tld.getShape(data, id).parentId, depth + 1) } diff --git a/state/commands/handle.ts b/state/commands/handle.ts index 0018c0aff..e5078b2ab 100644 --- a/state/commands/handle.ts +++ b/state/commands/handle.ts @@ -1,7 +1,7 @@ import Command from './command' import history from '../history' import { Data } from 'types' -import { getPage } from 'utils' +import tld from 'utils/tld' import { HandleSnapshot } from 'state/sessions/handle-session' import { getShapeUtils } from 'state/shape-utils' @@ -18,7 +18,7 @@ export default function handleCommand( do(data) { const { initialShape } = after - const page = getPage(data) + const page = tld.getPage(data) const shape = page.shapes[initialShape.id] getShapeUtils(shape) @@ -28,7 +28,7 @@ export default function handleCommand( undo(data) { const { initialShape } = before - const page = getPage(data) + const page = tld.getPage(data) page.shapes[initialShape.id] = initialShape }, }) diff --git a/state/commands/index.ts b/state/commands/index.ts index 14406eb37..b803e9445 100644 --- a/state/commands/index.ts +++ b/state/commands/index.ts @@ -4,7 +4,6 @@ import changePage from './change-page' import createPage from './create-page' import deletePage from './delete-page' import deleteSelected from './delete-selected' -import direct from './direct' import distribute from './distribute' import doublePointHandle from './double-point-handle' import draw from './draw' @@ -36,7 +35,6 @@ const commands = { createPage, deletePage, deleteSelected, - direct, distribute, doublePointHandle, draw, diff --git a/state/commands/move-to-page.ts b/state/commands/move-to-page.ts index 57ecffeed..7b907a133 100644 --- a/state/commands/move-to-page.ts +++ b/state/commands/move-to-page.ts @@ -1,24 +1,18 @@ import Command from './command' import history from '../history' import { Data } from 'types' -import { - getDocumentBranch, - getPage, - getPageState, - getSelectedIds, - setToArray, - uniqueArray, -} from 'utils' +import { setToArray, uniqueArray } from 'utils' +import tld from 'utils/tld' import { getShapeUtils } from 'state/shape-utils' import storage from 'state/storage' export default function moveToPageCommand(data: Data, newPageId: string): void { const { currentPageId: oldPageId } = data - const oldPage = getPage(data) - const selectedIds = setToArray(getSelectedIds(data)) + const oldPage = tld.getPage(data) + const selectedIds = setToArray(tld.getSelectedIds(data)) const idsToMove = uniqueArray( - ...selectedIds.flatMap((id) => getDocumentBranch(data, id)) + ...selectedIds.flatMap((id) => tld.getDocumentBranch(data, id)) ) const oldParentIds = Object.fromEntries( @@ -39,7 +33,7 @@ export default function moveToPageCommand(data: Data, newPageId: string): void { const fromPageId = oldPageId const toPageId = newPageId - const fromPage = getPage(data) + const fromPage = tld.getPage(data) // Get all of the selected shapes and their descendents const shapesToMove = idsToMove.map((id) => fromPage.shapes[id]) @@ -65,7 +59,7 @@ export default function moveToPageCommand(data: Data, newPageId: string): void { }) // Clear the current page state's selected ids - getPageState(data).selectedIds.clear() + tld.getPageState(data).selectedIds.clear() // Save the "from" page storage.savePage(data, data.document.id, fromPageId) @@ -74,7 +68,7 @@ export default function moveToPageCommand(data: Data, newPageId: string): void { storage.loadPage(data, toPageId) // The page we're moving the shapes to - const toPage = getPage(data) + const toPage = tld.getPage(data) // Add all of the selected shapes to the "from" page. shapesToMove.forEach((shape) => { @@ -89,7 +83,7 @@ export default function moveToPageCommand(data: Data, newPageId: string): void { }) // Select the selected ids on the new page - getPageState(data).selectedIds = new Set(selectedIds) + tld.getPageState(data).selectedIds = new Set(selectedIds) // Move to the new page data.currentPageId = toPageId @@ -98,7 +92,7 @@ export default function moveToPageCommand(data: Data, newPageId: string): void { const fromPageId = newPageId const toPageId = oldPageId - const fromPage = getPage(data) + const fromPage = tld.getPage(data) const shapesToMove = idsToMove.map((id) => fromPage.shapes[id]) @@ -119,13 +113,13 @@ export default function moveToPageCommand(data: Data, newPageId: string): void { delete fromPage.shapes[shape.id] }) - getPageState(data).selectedIds.clear() + tld.getPageState(data).selectedIds.clear() storage.savePage(data, data.document.id, fromPageId) storage.loadPage(data, toPageId) - const toPage = getPage(data) + const toPage = tld.getPage(data) shapesToMove.forEach((shape) => { toPage.shapes[shape.id] = shape @@ -144,7 +138,7 @@ export default function moveToPageCommand(data: Data, newPageId: string): void { } }) - getPageState(data).selectedIds = new Set(selectedIds) + tld.getPageState(data).selectedIds = new Set(selectedIds) data.currentPageId = toPageId }, diff --git a/state/commands/move.ts b/state/commands/move.ts index 5ba160a5c..0cd29898e 100644 --- a/state/commands/move.ts +++ b/state/commands/move.ts @@ -1,19 +1,14 @@ import Command from './command' import history from '../history' import { Data, MoveType, Shape } from 'types' -import { - forceIntegerChildIndices, - getChildren, - getPage, - getSelectedIds, - setToArray, -} from 'utils' +import { setToArray } from 'utils' +import tld from 'utils/tld' import { getShapeUtils } from 'state/shape-utils' export default function moveCommand(data: Data, type: MoveType): void { - const page = getPage(data) + const page = tld.getPage(data) - const selectedIds = setToArray(getSelectedIds(data)) + const selectedIds = setToArray(tld.getSelectedIds(data)) const initialIndices = Object.fromEntries( selectedIds.map((id) => [id, page.shapes[id].childIndex]) @@ -26,7 +21,7 @@ export default function moveCommand(data: Data, type: MoveType): void { category: 'canvas', manualSelection: true, do(data) { - const page = getPage(data) + const page = tld.getPage(data) const shapes = selectedIds.map((id) => page.shapes[id]) @@ -44,20 +39,20 @@ export default function moveCommand(data: Data, type: MoveType): void { switch (type) { case MoveType.ToFront: { for (const id in shapesByParentId) { - moveToFront(shapesByParentId[id], getChildren(data, id)) + moveToFront(shapesByParentId[id], tld.getChildren(data, id)) } break } case MoveType.ToBack: { for (const id in shapesByParentId) { - moveToBack(shapesByParentId[id], getChildren(data, id)) + moveToBack(shapesByParentId[id], tld.getChildren(data, id)) } break } case MoveType.Forward: { for (const id in shapesByParentId) { const visited = new Set() - const siblings = getChildren(data, id) + const siblings = tld.getChildren(data, id) shapesByParentId[id] .sort((a, b) => b.childIndex - a.childIndex) .forEach((shape) => moveForward(shape, siblings, visited)) @@ -67,7 +62,7 @@ export default function moveCommand(data: Data, type: MoveType): void { case MoveType.Backward: { for (const id in shapesByParentId) { const visited = new Set() - const siblings = getChildren(data, id) + const siblings = tld.getChildren(data, id) shapesByParentId[id] .sort((a, b) => a.childIndex - b.childIndex) .forEach((shape) => moveBackward(shape, siblings, visited)) @@ -77,7 +72,7 @@ export default function moveCommand(data: Data, type: MoveType): void { } }, undo(data) { - const page = getPage(data) + const page = tld.getPage(data) for (const id of selectedIds) { const shape = page.shapes[id] @@ -143,7 +138,7 @@ function moveForward(shape: Shape, siblings: Shape[], visited: Set) { : Math.ceil(nextSibling.childIndex + 1) if (nextIndex === nextSibling.childIndex) { - forceIntegerChildIndices(siblings) + tld.forceIntegerChildIndices(siblings) nextIndex = nextNextSibling ? (nextSibling.childIndex + nextNextSibling.childIndex) / 2 @@ -169,7 +164,7 @@ function moveBackward(shape: Shape, siblings: Shape[], visited: Set) { : nextSibling.childIndex / 2 if (shape.childIndex === nextSibling.childIndex) { - forceIntegerChildIndices(siblings) + tld.forceIntegerChildIndices(siblings) nextNextSibling ? (nextSibling.childIndex + nextNextSibling.childIndex) / 2 diff --git a/state/commands/mutate.ts b/state/commands/mutate.ts index 655a48d1a..b0c5f7598 100644 --- a/state/commands/mutate.ts +++ b/state/commands/mutate.ts @@ -2,7 +2,7 @@ import Command from './command' import history from '../history' import { Data, Shape } from 'types' import { getShapeUtils } from 'state/shape-utils' -import { getPage, updateParents } from 'utils' +import tld from 'utils/tld' // Used when changing the properties of one or more shapes, // without changing selection or deleting any shapes. @@ -19,27 +19,27 @@ export default function mutateShapesCommand( name, category: 'canvas', do(data) { - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) after.forEach((shape) => { shapes[shape.id] = shape getShapeUtils(shape).onSessionComplete(shape) }) - // updateParents( - // data, - // after.map((shape) => shape.id) - // ) + tld.updateParents( + data, + after.map((shape) => shape.id) + ) }, undo(data) { - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) before.forEach((shape) => { shapes[shape.id] = shape getShapeUtils(shape).onSessionComplete(shape) }) - updateParents( + tld.updateParents( data, before.map((shape) => shape.id) ) diff --git a/state/commands/nudge.ts b/state/commands/nudge.ts index 7de97ed59..fa2f57372 100644 --- a/state/commands/nudge.ts +++ b/state/commands/nudge.ts @@ -1,17 +1,11 @@ import Command from './command' import history from '../history' import { Data } from 'types' -import { getPage, getSelectedShapes } from 'utils' -import { getShapeUtils } from 'state/shape-utils' +import tld from 'utils/tld' import vec from 'utils/vec' export default function nudgeCommand(data: Data, delta: number[]): void { - const selectedShapes = getSelectedShapes(data) - const shapeBounds = Object.fromEntries( - selectedShapes.map( - (shape) => [shape.id, getShapeUtils(shape).getBounds(shape)] as const - ) - ) + const initialShapes = tld.getSelectedShapeSnapshot(data, () => null) history.execute( data, @@ -19,28 +13,22 @@ export default function nudgeCommand(data: Data, delta: number[]): void { name: 'nudge_shapes', category: 'canvas', do(data) { - const { shapes } = getPage(data) - - for (const id in shapeBounds) { - const shape = shapes[id] - getShapeUtils(shape).setProperty( - shape, - 'point', - vec.add(shape.point, delta) - ) - } + tld.mutateShapes( + data, + initialShapes.map((shape) => shape.id), + (shape, utils) => { + utils.setProperty(shape, 'point', vec.add(shape.point, delta)) + } + ) }, undo(data) { - const { shapes } = getPage(data) - - for (const id in shapeBounds) { - const shape = shapes[id] - getShapeUtils(shape).setProperty( - shape, - 'point', - vec.sub(shape.point, delta) - ) - } + tld.mutateShapes( + data, + initialShapes.map((shape) => shape.id), + (shape, utils) => { + utils.setProperty(shape, 'point', vec.sub(shape.point, delta)) + } + ) }, }) ) diff --git a/state/commands/paste.ts b/state/commands/paste.ts index 1d5183a6e..1d37a7774 100644 --- a/state/commands/paste.ts +++ b/state/commands/paste.ts @@ -1,21 +1,15 @@ import Command from './command' import history from '../history' import { Data, Shape } from 'types' -import { - getCommonBounds, - getPage, - getSelectedIds, - screenToWorld, - setSelectedIds, - setToArray, -} from 'utils' +import { getCommonBounds, setToArray } from 'utils' +import tld from 'utils/tld' import { uniqueId } from 'utils' import vec from 'utils/vec' import { getShapeUtils } from 'state/shape-utils' import state from 'state/state' export default function pasteCommand(data: Data, initialShapes: Shape[]): void { - const center = screenToWorld( + const center = tld.screenToWorld( [window.innerWidth / 2, window.innerHeight / 2], data ) @@ -32,7 +26,7 @@ export default function pasteCommand(data: Data, initialShapes: Shape[]): void { initialShapes.map((shape) => [shape.id, uniqueId()]) ) - const oldSelectedIds = setToArray(getSelectedIds(data)) + const oldSelectedIds = setToArray(tld.getSelectedIds(data)) history.execute( data, @@ -41,7 +35,7 @@ export default function pasteCommand(data: Data, initialShapes: Shape[]): void { category: 'canvas', manualSelection: true, do(data) { - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) let childIndex = (state.values.currentShapes[state.values.currentShapes.length - 1] @@ -62,14 +56,14 @@ export default function pasteCommand(data: Data, initialShapes: Shape[]): void { } } - setSelectedIds(data, Object.values(newIdMap)) + tld.setSelectedIds(data, Object.values(newIdMap)) }, undo(data) { - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) Object.values(newIdMap).forEach((id) => delete shapes[id]) - setSelectedIds(data, oldSelectedIds) + tld.setSelectedIds(data, oldSelectedIds) }, }) ) diff --git a/state/commands/reset-bounds.ts b/state/commands/reset-bounds.ts index 83eb092cd..679b8956c 100644 --- a/state/commands/reset-bounds.ts +++ b/state/commands/reset-bounds.ts @@ -1,14 +1,10 @@ import Command from './command' import history from '../history' import { Data } from 'types' -import { getPage, getSelectedShapes, updateParents } from 'utils' -import { current } from 'immer' -import { getShapeUtils } from 'state/shape-utils' +import tld from 'utils/tld' export default function resetBoundsCommand(data: Data): void { - const initialShapes = Object.fromEntries( - getSelectedShapes(current(data)).map((shape) => [shape.id, shape]) - ) + const initialShapes = tld.getSelectedShapeSnapshot(data) history.execute( data, @@ -16,21 +12,18 @@ export default function resetBoundsCommand(data: Data): void { name: 'reset_bounds', category: 'canvas', do(data) { - getSelectedShapes(data).forEach((shape) => { - if (shape.isLocked) return - getShapeUtils(shape).onBoundsReset(shape) - }) - - updateParents(data, Object.keys(initialShapes)) + tld.mutateShapes( + data, + initialShapes.map((shape) => shape.id), + (shape, utils) => void utils.onBoundsReset(shape) + ) }, undo(data) { - const page = getPage(data) - getSelectedShapes(data).forEach((shape) => { - if (shape.isLocked) return - page.shapes[shape.id] = initialShapes[shape.id] - }) - - updateParents(data, Object.keys(initialShapes)) + tld.mutateShapes( + data, + initialShapes.map((shape) => shape.id), + (_, __, i) => initialShapes[i] + ) }, }) ) diff --git a/state/commands/rotate-ccw.ts b/state/commands/rotate-ccw.ts index 1e76a3ea3..07be6c4de 100644 --- a/state/commands/rotate-ccw.ts +++ b/state/commands/rotate-ccw.ts @@ -1,12 +1,8 @@ import Command from './command' import history from '../history' import { Data } from 'types' -import { - getBoundsCenter, - getCommonBounds, - getPage, - getSelectedShapes, -} from 'utils' +import { getBoundsCenter, getCommonBounds } from 'utils' +import tld from 'utils/tld' import vec from 'utils/vec' import { getShapeUtils } from 'state/shape-utils' @@ -15,10 +11,10 @@ const PI2 = Math.PI * 2 export default function rotateCcwCommand(data: Data): void { const { boundsRotation } = data - const page = getPage(data) + const page = tld.getPage(data) const initialShapes = Object.fromEntries( - getSelectedShapes(data).map((shape) => { + tld.getSelectedShapes(data).map((shape) => { const bounds = getShapeUtils(shape).getBounds(shape) return [ shape.id, @@ -63,7 +59,7 @@ export default function rotateCcwCommand(data: Data): void { name: 'rotate_ccw', category: 'canvas', do(data) { - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) for (const id in nextShapes) { const shape = shapes[id] @@ -77,7 +73,7 @@ export default function rotateCcwCommand(data: Data): void { data.boundsRotation = nextboundsRotation }, undo(data) { - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) for (const id in initialShapes) { const { point, rotation } = initialShapes[id] diff --git a/state/commands/rotate.ts b/state/commands/rotate.ts index 9d72f96df..25c1ed2f1 100644 --- a/state/commands/rotate.ts +++ b/state/commands/rotate.ts @@ -2,8 +2,8 @@ import Command from './command' import history from '../history' import { Data } from 'types' import { RotateSnapshot } from 'state/sessions/rotate-session' -import { getPage } from 'utils' import { getShapeUtils } from 'state/shape-utils' +import tld from 'utils/tld' export default function rotateCommand( data: Data, @@ -16,7 +16,7 @@ export default function rotateCommand( name: 'rotate_shapes', category: 'canvas', do(data) { - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) for (const { id, point, rotation } of after.initialShapes) { const shape = shapes[id] @@ -29,7 +29,7 @@ export default function rotateCommand( data.boundsRotation = after.boundsRotation }, undo(data) { - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) for (const { id, point, rotation } of before.initialShapes) { const shape = shapes[id] diff --git a/state/commands/stretch.ts b/state/commands/stretch.ts index 2915284c2..3b6136d2d 100644 --- a/state/commands/stretch.ts +++ b/state/commands/stretch.ts @@ -1,11 +1,14 @@ import Command from './command' import history from '../history' import { StretchType, Data, Corner } from 'types' -import { deepClone, getCommonBounds, getPage, getSelectedShapes } from 'utils' +import { deepClone, getCommonBounds } from 'utils' +import tld from 'utils/tld' import { getShapeUtils } from 'state/shape-utils' export default function stretchCommand(data: Data, type: StretchType): void { - const initialShapes = getSelectedShapes(data).map((shape) => deepClone(shape)) + const initialShapes = tld + .getSelectedShapes(data) + .map((shape) => deepClone(shape)) const snapshot = Object.fromEntries( initialShapes.map((shape) => [ @@ -27,7 +30,7 @@ export default function stretchCommand(data: Data, type: StretchType): void { name: 'stretched_shapes', category: 'canvas', do(data) { - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) switch (type) { case StretchType.Horizontal: { @@ -75,7 +78,7 @@ export default function stretchCommand(data: Data, type: StretchType): void { } }, undo(data) { - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) initialShapes.forEach((shape) => (shapes[shape.id] = shape)) }, }) diff --git a/state/commands/style.ts b/state/commands/style.ts index b5161d69f..be5d017b5 100644 --- a/state/commands/style.ts +++ b/state/commands/style.ts @@ -1,7 +1,8 @@ import Command from './command' import history from '../history' import { Data, ShapeStyles } from 'types' -import { getDocumentBranch, getPage, getSelectedIds, setToArray } from 'utils' +import tld from 'utils/tld' +import { setToArray } from 'utils' import { getShapeUtils } from 'state/shape-utils' import { current } from 'immer' @@ -10,12 +11,12 @@ export default function styleCommand( styles: Partial ): void { const cData = current(data) - const page = getPage(cData) + const page = tld.getPage(cData) - const selectedIds = setToArray(getSelectedIds(data)) + const selectedIds = setToArray(tld.getSelectedIds(data)) const shapesToStyle = selectedIds - .flatMap((id) => getDocumentBranch(data, id)) + .flatMap((id) => tld.getDocumentBranch(data, id)) .map((id) => page.shapes[id]) history.execute( @@ -25,7 +26,7 @@ export default function styleCommand( category: 'canvas', manualSelection: true, do(data) { - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) for (const { id } of shapesToStyle) { const shape = shapes[id] @@ -33,7 +34,7 @@ export default function styleCommand( } }, undo(data) { - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) for (const { id, style } of shapesToStyle) { const shape = shapes[id] diff --git a/state/commands/toggle.ts b/state/commands/toggle.ts index 5b341183b..f6c113e13 100644 --- a/state/commands/toggle.ts +++ b/state/commands/toggle.ts @@ -1,7 +1,7 @@ import Command from './command' import history from '../history' import { Data, Shape } from 'types' -import { getPage, getSelectedShapes } from 'utils' +import tld from 'utils/tld' import { getShapeUtils } from 'state/shape-utils' import { PropsOfType } from 'types' @@ -9,7 +9,7 @@ export default function toggleCommand( data: Data, prop: PropsOfType ): void { - const selectedShapes = getSelectedShapes(data) + const selectedShapes = tld.getSelectedShapes(data) const isAllToggled = selectedShapes.every((shape) => shape[prop]) const initialShapes = Object.fromEntries( selectedShapes.map((shape) => [shape.id, shape[prop]]) @@ -21,7 +21,7 @@ export default function toggleCommand( name: 'toggle_prop', category: 'canvas', do(data) { - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) for (const id in initialShapes) { const shape = shapes[id] @@ -33,7 +33,7 @@ export default function toggleCommand( } }, undo(data) { - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) for (const id in initialShapes) { const shape = shapes[id] diff --git a/state/commands/transform-single.ts b/state/commands/transform-single.ts index c9c134e44..58d54d2ee 100644 --- a/state/commands/transform-single.ts +++ b/state/commands/transform-single.ts @@ -3,7 +3,7 @@ import history from '../history' import { Data } from 'types' import { current } from 'immer' import { TransformSingleSnapshot } from 'state/sessions/transform-single-session' -import { getPage, setSelectedIds, updateParents } from 'utils' +import tld from 'utils/tld' export default function transformSingleCommand( data: Data, @@ -11,7 +11,7 @@ export default function transformSingleCommand( after: TransformSingleSnapshot, isCreating: boolean ): void { - const shape = current(getPage(data).shapes[after.id]) + const shape = current(tld.getPage(data).shapes[after.id]) history.execute( data, @@ -22,27 +22,27 @@ export default function transformSingleCommand( do(data) { const { id } = after - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) - setSelectedIds(data, [id]) + tld.setSelectedIds(data, [id]) shapes[id] = shape - updateParents(data, [id]) + tld.updateParents(data, [id]) }, undo(data) { const { id, initialShape } = before - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) if (isCreating) { - setSelectedIds(data, []) + tld.setSelectedIds(data, []) delete shapes[id] } else { - const page = getPage(data) + const page = tld.getPage(data) page.shapes[id] = initialShape - updateParents(data, [id]) - setSelectedIds(data, [id]) + tld.updateParents(data, [id]) + tld.setSelectedIds(data, [id]) } }, }) diff --git a/state/commands/transform.ts b/state/commands/transform.ts index 545916d52..8e3b15301 100644 --- a/state/commands/transform.ts +++ b/state/commands/transform.ts @@ -2,7 +2,7 @@ import Command from './command' import history from '../history' import { Data } from 'types' import { TransformSnapshot } from 'state/sessions/transform-session' -import { getPage, updateParents } from 'utils' +import tld from 'utils/tld' export default function transformCommand( data: Data, @@ -17,23 +17,23 @@ export default function transformCommand( do(data) { const { shapeBounds } = after - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) for (const id in shapeBounds) { shapes[id] = shapeBounds[id].initialShape } - updateParents(data, Object.keys(shapeBounds)) + tld.updateParents(data, Object.keys(shapeBounds)) }, undo(data) { const { shapeBounds } = before - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) for (const id in shapeBounds) { shapes[id] = shapeBounds[id].initialShape } - updateParents(data, Object.keys(shapeBounds)) + tld.updateParents(data, Object.keys(shapeBounds)) }, }) ) diff --git a/state/commands/translate.ts b/state/commands/translate.ts index 9ae8c4383..1a1e15d7f 100644 --- a/state/commands/translate.ts +++ b/state/commands/translate.ts @@ -2,12 +2,7 @@ import Command from './command' import history from '../history' import { TranslateSnapshot } from 'state/sessions/translate-session' import { Data } from 'types' -import { - getDocumentBranch, - getPage, - setSelectedIds, - updateParents, -} from 'utils' +import tld from 'utils/tld' import { getShapeUtils } from 'state/shape-utils' export default function translateCommand( @@ -26,7 +21,7 @@ export default function translateCommand( if (initial) return const { initialShapes } = after - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) // Restore clones to document if (isCloning) { @@ -47,31 +42,31 @@ export default function translateCommand( // Move shapes (these initialShapes will include clones if any) for (const { id, point } of initialShapes) { - getDocumentBranch(data, id).forEach((id) => { + tld.getDocumentBranch(data, id).forEach((id) => { const shape = shapes[id] getShapeUtils(shape).translateTo(shape, point) }) } // Set selected shapes - setSelectedIds( + tld.setSelectedIds( data, initialShapes.map((s) => s.id) ) // Update parents - updateParents( + tld.updateParents( data, initialShapes.map((s) => s.id) ) }, undo(data) { const { initialShapes, clones, initialParents } = before - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) // Move shapes back to where they started for (const { id, point } of initialShapes) { - getDocumentBranch(data, id).forEach((id) => { + tld.getDocumentBranch(data, id).forEach((id) => { const shape = shapes[id] getShapeUtils(shape).translateTo(shape, point) }) @@ -81,7 +76,7 @@ export default function translateCommand( if (isCloning) for (const { id } of clones) delete shapes[id] // Set selected shapes - setSelectedIds( + tld.setSelectedIds( data, initialShapes.map((s) => s.id) ) @@ -93,7 +88,7 @@ export default function translateCommand( }) // Update parents - updateParents( + tld.updateParents( data, initialShapes.map((s) => s.id) ) diff --git a/state/commands/ungroup.ts b/state/commands/ungroup.ts index bdf832525..00a69d02b 100644 --- a/state/commands/ungroup.ts +++ b/state/commands/ungroup.ts @@ -1,17 +1,18 @@ import Command from './command' import history from '../history' import { Data, ShapeType } from 'types' -import { getPage, getSelectedShapes, setSelectedIds } from 'utils' -import { current } from 'immer' import { getShapeUtils } from 'state/shape-utils' +import tld from 'utils/tld' +import { deepClone } from 'utils' export default function ungroupCommand(data: Data): void { - const cData = current(data) - const { currentPageId } = cData + const { currentPageId } = data - const selectedGroups = getSelectedShapes(cData) + const selectedGroups = tld + .getSelectedShapes(data) .filter((shape) => shape.type === ShapeType.Group) .sort((a, b) => a.childIndex - b.childIndex) + .map((shape) => deepClone(shape)) // Are all of the shapes already in the same group? // - ungroup the shapes @@ -24,7 +25,7 @@ export default function ungroupCommand(data: Data): void { name: 'ungroup_shapes', category: 'canvas', do(data) { - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) // Remove shapes from old parents for (const oldGroupShape of selectedGroups) { @@ -62,13 +63,13 @@ export default function ungroupCommand(data: Data): void { ) }) - setSelectedIds(data, oldGroupShape.children) + tld.setSelectedIds(data, oldGroupShape.children) delete shapes[oldGroupShape.id] } }, undo(data) { - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) selectedGroups.forEach((group) => { shapes[group.id] = group @@ -80,7 +81,7 @@ export default function ungroupCommand(data: Data): void { }) }) - setSelectedIds( + tld.setSelectedIds( data, selectedGroups.map((g) => g.id) ) @@ -94,5 +95,5 @@ export default function ungroupCommand(data: Data): void { // return depth // } -// return getShapeDepth(data, getShape(data, id).parentId, depth + 1) +// return getShapeDepth(data, tld.getShape(data, id).parentId, depth + 1) // } diff --git a/state/hacks.ts b/state/hacks.ts index 44a172571..a23bc03e8 100644 --- a/state/hacks.ts +++ b/state/hacks.ts @@ -1,11 +1,6 @@ import { DrawShape, PointerInfo } from 'types' -import { - getCameraZoom, - getCurrentCamera, - getSelectedIds, - screenToWorld, - setToArray, -} from 'utils' +import { setToArray } from 'utils' +import tld from 'utils/tld' import { freeze } from 'immer' import session from './session' import state from './state' @@ -24,12 +19,12 @@ export function fastDrawUpdate(info: PointerInfo): void { session.update( data, - screenToWorld(info.point, data), + tld.screenToWorld(info.point, data), info.pressure, info.shiftKey ) - const selectedId = setToArray(getSelectedIds(data))[0] + const selectedId = setToArray(tld.getSelectedIds(data))[0] const shape = data.document.pages[data.currentPageId].shapes[ selectedId @@ -45,7 +40,7 @@ export function fastDrawUpdate(info: PointerInfo): void { export function fastPanUpdate(delta: number[]): void { const data = { ...state.data } - const camera = getCurrentCamera(data) + const camera = tld.getCurrentCamera(data) camera.point = vec.sub(camera.point, vec.div(delta, camera.zoom)) data.pageStates[data.currentPageId].camera = { ...camera } @@ -55,13 +50,13 @@ export function fastPanUpdate(delta: number[]): void { export function fastZoomUpdate(point: number[], delta: number): void { const data = { ...state.data } - const camera = getCurrentCamera(data) + const camera = tld.getCurrentCamera(data) const next = camera.zoom - (delta / 100) * camera.zoom - const p0 = screenToWorld(point, data) - camera.zoom = getCameraZoom(next) - const p1 = screenToWorld(point, data) + const p0 = tld.screenToWorld(point, data) + camera.zoom = tld.getCameraZoom(next) + const p1 = tld.screenToWorld(point, data) camera.point = vec.add(camera.point, vec.sub(p1, p0)) data.pageStates[data.currentPageId].camera = { ...camera } @@ -75,15 +70,15 @@ export function fastPinchCamera( distanceDelta: number ): void { const data = { ...state.data } - const camera = getCurrentCamera(data) + const camera = tld.getCurrentCamera(data) camera.point = vec.sub(camera.point, vec.div(delta, camera.zoom)) const next = camera.zoom - (distanceDelta / 350) * camera.zoom - const p0 = screenToWorld(point, data) - camera.zoom = getCameraZoom(next) - const p1 = screenToWorld(point, data) + const p0 = tld.screenToWorld(point, data) + camera.zoom = tld.getCameraZoom(next) + const p1 = tld.screenToWorld(point, data) camera.point = vec.add(camera.point, vec.sub(p1, p0)) const pageState = data.pageStates[data.currentPageId] @@ -97,7 +92,7 @@ export function fastPinchCamera( export function fastBrushSelect(point: number[]): void { const data = { ...state.data } - session.update(data, screenToWorld(point, data)) + session.update(data, tld.screenToWorld(point, data)) state.forceData(freeze(data)) } @@ -107,7 +102,7 @@ export function fastTranslate(info: PointerInfo): void { session.update( data, - screenToWorld(info.point, data), + tld.screenToWorld(info.point, data), info.shiftKey, info.altKey ) @@ -120,7 +115,7 @@ export function fastTransform(info: PointerInfo): void { session.update( data, - screenToWorld(info.point, data), + tld.screenToWorld(info.point, data), info.shiftKey ) diff --git a/state/pusher/client.ts b/state/pusher/client-pusher.ts similarity index 100% rename from state/pusher/client.ts rename to state/pusher/client-pusher.ts diff --git a/state/pusher/client-supa.ts b/state/pusher/client-supa.ts new file mode 100644 index 000000000..a85578df4 --- /dev/null +++ b/state/pusher/client-supa.ts @@ -0,0 +1,140 @@ +/* eslint-disable no-console */ +import state from 'state/state' +// import { Shape } from 'types' +import { RealtimeClient, RealtimeSubscription } from '@supabase/realtime-js' + +class RoomClient { + id: string + roomId: string + client: RealtimeClient + channel: RealtimeSubscription + lastCursorEventTime = 0 + + constructor() { + // Create client + this.client = new RealtimeClient( + 'https://mntnflsepfmpvthazvvu.supabase.com' + ) + + // Set event listeners + this.client.onOpen(() => + state.send('RT_CHANGED_STATUS', { status: 'Socket opened.' }) + ) + + this.client.onClose(() => + state.send('RT_CHANGED_STATUS', { status: 'Socket closed.' }) + ) + + this.client.onError((e: Error) => + state.send('RT_CHANGED_STATUS', { status: `Socket error: ${e.message}` }) + ) + + // Connect to client + this.client.connect() + } + + connect(roomId: string) { + this.roomId = roomId + + // Unsubscribe from any existing channel + + if (this.channel !== undefined) { + this.channel.unsubscribe() + delete this.channel + } + + // Create a new channel for this room id + + this.channel = this.client.channel(`realtime:public:${this.roomId}`) + this.channel.on('*', (e: any) => console.log(e)) + this.channel.on('INSERT', (e: any) => console.log(e)) + this.channel.on('UPDATE', (e: any) => console.log(e)) + this.channel.on('DELETE', (e: any) => console.log(e)) + + // Subscribe to the channel + this.channel + .subscribe() + .receive('ok', () => console.log('Connected.')) + .receive('error', () => console.log('Failed.')) + .receive('timeout', () => console.log('Timed out, retrying.')) + + // Old + + // this.channel = this.pusher.subscribe( + // this.room + // ) as PusherTypes.PresenceChannel + + // this.channel.bind('pusher:subscription_error', (err: string) => { + // console.warn(err) + // state.send('RT_CHANGED_STATUS', { status: 'subscription-error' }) + // }) + + // this.channel.bind('pusher:subscription_succeeded', () => { + // const me = this.channel.members.me + // const userId = me.id + + // this.id = userId + + // state.send('RT_CHANGED_STATUS', { status: 'subscribed' }) + // }) + + // this.channel.bind( + // 'created_shape', + // (payload: { id: string; pageId: string; shape: Shape }) => { + // if (payload.id === this.id) return + // state.send('RT_CREATED_SHAPE', payload) + // } + // ) + + // this.channel.bind( + // 'deleted_shape', + // (payload: { id: string; pageId: string; shape: Shape }) => { + // if (payload.id === this.id) return + // state.send('RT_DELETED_SHAPE', payload) + // } + // ) + + // this.channel.bind( + // 'edited_shape', + // (payload: { id: string; pageId: string; change: Partial }) => { + // if (payload.id === this.id) return + // state.send('RT_EDITED_SHAPE', payload) + // } + // ) + + // this.channel.bind( + // 'client-moved-cursor', + // (payload: { id: string; pageId: string; point: number[] }) => { + // if (payload.id === this.id) return + // state.send('RT_MOVED_CURSOR', payload) + // } + // ) + } + + disconnect() { + this.channel.unsubscribe() + this.client.disconnect() + } + + reconnect() { + this.connect(this.roomId) + } + + moveCursor(pageId: string, point: number[]) { + if (!this.channel) return + + const now = Date.now() + + if (now - this.lastCursorEventTime > 200) { + this.lastCursorEventTime = now + + this.channel?.trigger('client-moved-cursor', { + id: this.id, + pageId, + point, + }) + } + } +} + +export default new RoomClient() diff --git a/state/sessions/arrow-session.ts b/state/sessions/arrow-session.ts index 3b5d4fbbb..879c46537 100644 --- a/state/sessions/arrow-session.ts +++ b/state/sessions/arrow-session.ts @@ -3,14 +3,9 @@ import vec from 'utils/vec' import BaseSession from './base-session' import commands from 'state/commands' import { current } from 'immer' -import { - getBoundsFromPoints, - getPage, - getSelectedIds, - setToArray, - updateParents, -} from 'utils' +import { getBoundsFromPoints, setToArray } from 'utils' import { getShapeUtils } from 'state/shape-utils' +import tld from 'utils/tld' export default class ArrowSession extends BaseSession { points: number[][] @@ -56,7 +51,7 @@ export default class ArrowSession extends BaseSession { } } - const shape = getPage(data).shapes[id] as ArrowShape + const shape = tld.getPage(data).shapes[id] as ArrowShape getShapeUtils(shape).onHandleChange(shape, { end: { @@ -65,25 +60,25 @@ export default class ArrowSession extends BaseSession { }, }) - updateParents(data, [shape.id]) + tld.updateParents(data, [shape.id]) } cancel(data: Data): void { const { id, initialShape } = this.snapshot - const shape = getPage(data).shapes[id] as ArrowShape + const shape = tld.getPage(data).shapes[id] as ArrowShape getShapeUtils(shape) .onHandleChange(shape, { end: initialShape.handles.end }) .setProperty(shape, 'point', initialShape.point) - updateParents(data, [shape.id]) + tld.updateParents(data, [shape.id]) } complete(data: Data): void { const { id } = this.snapshot - const shape = getPage(data).shapes[id] as ArrowShape + const shape = tld.getPage(data).shapes[id] as ArrowShape const { start, end, bend } = shape.handles @@ -115,12 +110,12 @@ export default class ArrowSession extends BaseSession { // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export function getArrowSnapshot(data: Data, id: string) { - const initialShape = getPage(current(data)).shapes[id] as ArrowShape + const initialShape = tld.getPage(current(data)).shapes[id] as ArrowShape return { id, initialShape, - selectedIds: setToArray(getSelectedIds(data)), + selectedIds: setToArray(tld.getSelectedIds(data)), currentPageId: data.currentPageId, } } diff --git a/state/sessions/brush-session.ts b/state/sessions/brush-session.ts index 55274fb2d..b3953faf1 100644 --- a/state/sessions/brush-session.ts +++ b/state/sessions/brush-session.ts @@ -2,15 +2,9 @@ import { current } from 'immer' import { Bounds, Data, ShapeType } from 'types' import BaseSession from './base-session' import { getShapeUtils } from 'state/shape-utils' -import { - getBoundsFromPoints, - getPageState, - getShapes, - getTopParentId, - setSelectedIds, - setToArray, -} from 'utils' +import { getBoundsFromPoints, setToArray } from 'utils' import vec from 'utils/vec' +import tld from 'utils/tld' export default class BrushSession extends BaseSession { origin: number[] @@ -51,14 +45,14 @@ export default class BrushSession extends BaseSession { } } - getPageState(data).selectedIds = selectedIds + tld.getPageState(data).selectedIds = selectedIds data.brush = brushBounds } cancel = (data: Data): void => { data.brush = undefined - setSelectedIds(data, this.snapshot.selectedIds) + tld.setSelectedIds(data, this.snapshot.selectedIds) } complete = (data: Data): void => { @@ -74,9 +68,10 @@ export default class BrushSession extends BaseSession { // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export function getBrushSnapshot(data: Data) { const cData = current(data) - const { selectedIds } = getPageState(cData) + const { selectedIds } = tld.getPageState(cData) - const shapesToTest = getShapes(cData) + const shapesToTest = tld + .getShapes(cData) .filter((shape) => shape.type !== ShapeType.Group && !shape.isHidden) .filter( (shape) => !(selectedIds.has(shape.id) || selectedIds.has(shape.parentId)) @@ -89,7 +84,7 @@ export function getBrushSnapshot(data: Data) { return [ shape.id, { - selectId: getTopParentId(cData, shape.id), + selectId: tld.getTopParentId(cData, shape.id), test: (bounds: Bounds) => getShapeUtils(shape).hitTestBounds(shape, bounds), }, diff --git a/state/sessions/direction-session.ts b/state/sessions/direction-session.ts index ff25e8149..8844b8878 100644 --- a/state/sessions/direction-session.ts +++ b/state/sessions/direction-session.ts @@ -1,9 +1,10 @@ -import { Data, LineShape, RayShape } from 'types' +import { Data, Shape } from 'types' import vec from 'utils/vec' import BaseSession from './base-session' import commands from 'state/commands' -import { current } from 'immer' -import { getPage, getSelectedIds } from 'utils' +import tld from 'utils/tld' +import { deepClone } from 'utils' +import { getShapeUtils } from 'state/shape-utils' export default class DirectionSession extends BaseSession { delta = [0, 0] @@ -17,48 +18,52 @@ export default class DirectionSession extends BaseSession { } update(data: Data, point: number[]): void { - const { shapes } = this.snapshot + const page = tld.getPage(data) - const page = getPage(data) + this.snapshot.forEach((initialShape) => { + const shape = page.shapes[initialShape.id] - for (const { id } of shapes) { - const shape = page.shapes[id] as RayShape | LineShape - - shape.direction = vec.uni(vec.vec(shape.point, point)) - } + if ('direction' in shape) { + getShapeUtils(shape).setProperty( + shape, + 'direction', + vec.uni(vec.vec(shape.point, point)) + ) + } + }) } cancel(data: Data): void { - const page = getPage(data) + const page = tld.getPage(data) - for (const { id, direction } of this.snapshot.shapes) { - const shape = page.shapes[id] as RayShape | LineShape - shape.direction = direction - } + this.snapshot.forEach((initialShape) => { + const shape = page.shapes[initialShape.id] + + if ('direction' in shape && 'direction' in initialShape) { + getShapeUtils(shape).setProperty( + shape, + 'direction', + initialShape.direction + ) + } + }) } complete(data: Data): void { - commands.direct(data, this.snapshot, getDirectionSnapshot(data)) + commands.mutate( + data, + this.snapshot, + getDirectionSnapshot(data), + 'change_direction' + ) } } -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export function getDirectionSnapshot(data: Data) { - const { shapes } = getPage(current(data)) - - const snapshapes: { id: string; direction: number[] }[] = [] - - getSelectedIds(data).forEach((id) => { - const shape = shapes[id] - if ('direction' in shape) { - snapshapes.push({ id: shape.id, direction: shape.direction }) - } - }) - - return { - currentPageId: data.currentPageId, - shapes: snapshapes, - } +export function getDirectionSnapshot(data: Data): Shape[] { + return tld + .getSelectedShapes(data) + .filter((shape) => 'direction' in shape) + .map((shape) => deepClone(shape)) } export type DirectionSnapshot = ReturnType diff --git a/state/sessions/draw-session.ts b/state/sessions/draw-session.ts index 3fa3f8f13..a756aaa80 100644 --- a/state/sessions/draw-session.ts +++ b/state/sessions/draw-session.ts @@ -2,7 +2,8 @@ import { current } from 'immer' import { Data, DrawShape } from 'types' import BaseSession from './base-session' import { getShapeUtils } from 'state/shape-utils' -import { getBoundsFromPoints, getPage, getShape, updateParents } from 'utils' +import { getBoundsFromPoints } from 'utils' +import tld from 'utils/tld' import vec from 'utils/vec' import commands from 'state/commands' @@ -27,11 +28,11 @@ export default class DrawSession extends BaseSession { // points, this single point will be interpreted as a "dot" shape. this.points = [[0, 0, 0.5]] - const shape = getPage(data).shapes[id] + const shape = tld.getPage(data).shapes[id] getShapeUtils(shape).translateTo(shape, point) - updateParents(data, [shape.id]) + tld.updateParents(data, [shape.id]) } update = ( @@ -103,26 +104,26 @@ export default class DrawSession extends BaseSession { if (this.points.length <= 2) return // Update the points and update the shape's parents. - const shape = getShape(data, snapshot.id) as DrawShape + const shape = tld.getShape(data, snapshot.id) as DrawShape // Note: Normally we would want to spread the points to create a new // array, however we create the new array in hacks/fastDrawUpdate. getShapeUtils(shape).setProperty(shape, 'points', this.points) - updateParents(data, [shape.id]) + tld.updateParents(data, [shape.id]) } cancel = (data: Data): void => { const { snapshot } = this - const shape = getShape(data, snapshot.id) as DrawShape + const shape = tld.getShape(data, snapshot.id) as DrawShape getShapeUtils(shape).translateTo(shape, snapshot.point) getShapeUtils(shape).setProperty(shape, 'points', snapshot.points) - updateParents(data, [shape.id]) + tld.updateParents(data, [shape.id]) } complete = (data: Data): void => { const { snapshot } = this - const page = getPage(data) + const page = tld.getPage(data) const shape = page.shapes[snapshot.id] as DrawShape if (shape.points.length < this.points.length) { @@ -131,7 +132,7 @@ export default class DrawSession extends BaseSession { getShapeUtils(shape).onSessionComplete(shape) - updateParents(data, [shape.id]) + tld.updateParents(data, [shape.id]) commands.draw(data, this.snapshot.id) } @@ -139,7 +140,7 @@ export default class DrawSession extends BaseSession { // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export function getDrawSnapshot(data: Data, shapeId: string) { - const page = getPage(current(data)) + const page = tld.getPage(current(data)) const { points, point } = page.shapes[shapeId] as DrawShape return { diff --git a/state/sessions/edit-session.ts b/state/sessions/edit-session.ts index 784e1273a..addeb4646 100644 --- a/state/sessions/edit-session.ts +++ b/state/sessions/edit-session.ts @@ -2,8 +2,8 @@ import { Data, Shape } from 'types' import BaseSession from './base-session' import commands from 'state/commands' import { current } from 'immer' -import { getPage, getSelectedShapes, getShape } from 'utils' import { getShapeUtils } from 'state/shape-utils' +import tld from 'utils/tld' export default class EditSession extends BaseSession { snapshot: EditSnapshot @@ -15,7 +15,7 @@ export default class EditSession extends BaseSession { update(data: Data, change: Partial): void { const initialShape = this.snapshot.initialShape - const shape = getShape(data, initialShape.id) + const shape = tld.getShape(data, initialShape.id) const utils = getShapeUtils(shape) Object.entries(change).forEach(([key, value]) => { utils.setProperty(shape, key as keyof Shape, value as Shape[keyof Shape]) @@ -24,7 +24,7 @@ export default class EditSession extends BaseSession { cancel(data: Data): void { const initialShape = this.snapshot.initialShape - const page = getPage(data) + const page = tld.getPage(data) page.shapes[initialShape.id] = initialShape } @@ -35,7 +35,7 @@ export default class EditSession extends BaseSession { // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export function getEditSnapshot(data: Data) { - const initialShape = getSelectedShapes(current(data))[0] + const initialShape = tld.getSelectedShapes(current(data))[0] return { currentPageId: data.currentPageId, diff --git a/state/sessions/handle-session.ts b/state/sessions/handle-session.ts index 92f238643..471e06991 100644 --- a/state/sessions/handle-session.ts +++ b/state/sessions/handle-session.ts @@ -3,7 +3,7 @@ import vec from 'utils/vec' import BaseSession from './base-session' import commands from 'state/commands' import { current } from 'immer' -import { getPage } from 'utils' +import tld from 'utils/tld' import { getShapeUtils } from 'state/shape-utils' export default class HandleSession extends BaseSession { @@ -19,7 +19,7 @@ export default class HandleSession extends BaseSession { update(data: Data, point: number[], isAligned: boolean): void { const { handleId, initialShape } = this.snapshot - const shape = getPage(data).shapes[initialShape.id] + const shape = tld.getPage(data).shapes[initialShape.id] const delta = vec.vec(this.origin, point) @@ -47,7 +47,7 @@ export default class HandleSession extends BaseSession { cancel(data: Data): void { const { initialShape } = this.snapshot - getPage(data).shapes[initialShape.id] = initialShape + tld.getPage(data).shapes[initialShape.id] = initialShape } complete(data: Data): void { @@ -69,7 +69,7 @@ export function getHandleSnapshot( shapeId: string, handleId: string ) { - const initialShape = getPage(current(data)).shapes[shapeId] + const initialShape = tld.getPage(current(data)).shapes[shapeId] return { currentPageId: data.currentPageId, diff --git a/state/sessions/rotate-session.ts b/state/sessions/rotate-session.ts index d236c4fe2..19585d72f 100644 --- a/state/sessions/rotate-session.ts +++ b/state/sessions/rotate-session.ts @@ -7,14 +7,9 @@ import { clampToRotationToSegments, getBoundsCenter, getCommonBounds, - getPage, - getRotatedBounds, - getShapeBounds, - updateParents, - getDocumentBranch, setToArray, - getSelectedIds, } from 'utils' +import tld from 'utils/tld' import { getShapeUtils } from 'state/shape-utils' const PI2 = Math.PI * 2 @@ -34,7 +29,7 @@ export default class RotateSession extends BaseSession { update(data: Data, point: number[], isLocked: boolean): void { const { commonBoundsCenter, initialShapes } = this.snapshot - const page = getPage(data) + const page = tld.getPage(data) const a1 = vec.angle(commonBoundsCenter, this.origin) const a2 = vec.angle(commonBoundsCenter, point) @@ -69,7 +64,7 @@ export default class RotateSession extends BaseSession { .translateTo(shape, nextPoint) } - updateParents( + tld.updateParents( data, initialShapes.map((s) => s.id) ) @@ -77,7 +72,7 @@ export default class RotateSession extends BaseSession { cancel(data: Data): void { const { initialShapes } = this.snapshot - const page = getPage(data) + const page = tld.getPage(data) for (const { id, point, rotation } of initialShapes) { const shape = page.shapes[id] @@ -86,7 +81,7 @@ export default class RotateSession extends BaseSession { .translateTo(shape, point) } - updateParents( + tld.updateParents( data, initialShapes.map((s) => s.id) ) @@ -101,16 +96,18 @@ export default class RotateSession extends BaseSession { // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export function getRotateSnapshot(data: Data) { const cData = current(data) - const page = getPage(cData) + const page = tld.getPage(cData) - const initialShapes = setToArray(getSelectedIds(data)) - .flatMap((id) => getDocumentBranch(cData, id).map((id) => page.shapes[id])) + const initialShapes = setToArray(tld.getSelectedIds(data)) + .flatMap((id) => + tld.getDocumentBranch(cData, id).map((id) => page.shapes[id]) + ) .filter((shape) => !shape.isLocked) const hasUnlockedShapes = initialShapes.length > 0 const shapesBounds = Object.fromEntries( - initialShapes.map((shape) => [shape.id, getShapeBounds(shape)]) + initialShapes.map((shape) => [shape.id, tld.getShapeBounds(shape)]) ) const bounds = getCommonBounds(...Object.values(shapesBounds)) @@ -131,7 +128,7 @@ export function getRotateSnapshot(data: Data) { const rotationOffset = vec.sub( center, - getBoundsCenter(getRotatedBounds(shape)) + getBoundsCenter(tld.getRotatedBounds(shape)) ) return { diff --git a/state/sessions/transform-session.ts b/state/sessions/transform-session.ts index 79355f57b..d328b3f19 100644 --- a/state/sessions/transform-session.ts +++ b/state/sessions/transform-session.ts @@ -3,18 +3,15 @@ import vec from 'utils/vec' import BaseSession from './base-session' import commands from 'state/commands' import { getShapeUtils } from 'state/shape-utils' +import tld from 'utils/tld' import { deepClone, getBoundsCenter, getBoundsFromPoints, getCommonBounds, - getDocumentBranch, - getPage, getRelativeTransformedBoundingBox, - getSelectedIds, getTransformedBoundingBox, setToArray, - updateParents, } from 'utils' export default class TransformSession extends BaseSession { @@ -36,7 +33,7 @@ export default class TransformSession extends BaseSession { const { shapeBounds, initialBounds, isAllAspectRatioLocked } = this.snapshot - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) const newBoundingBox = getTransformedBoundingBox( initialBounds, @@ -76,13 +73,13 @@ export default class TransformSession extends BaseSession { shapes[id] = { ...shape } } - updateParents(data, Object.keys(shapeBounds)) + tld.updateParents(data, Object.keys(shapeBounds)) } cancel(data: Data): void { const { shapeBounds } = this.snapshot - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) for (const id in shapeBounds) { const shape = shapes[id] @@ -98,7 +95,7 @@ export default class TransformSession extends BaseSession { transformOrigin, }) - updateParents(data, Object.keys(shapeBounds)) + tld.updateParents(data, Object.keys(shapeBounds)) } } @@ -107,7 +104,7 @@ export default class TransformSession extends BaseSession { if (!hasUnlockedShapes) return - const page = getPage(data) + const page = tld.getPage(data) const finalShapes = initialShapes.map((shape) => deepClone(page.shapes[shape.id]) @@ -120,10 +117,12 @@ export default class TransformSession extends BaseSession { // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export function getTransformSnapshot(data: Data, transformType: Edge | Corner) { const { currentPageId } = data - const page = getPage(data) + const page = tld.getPage(data) - const initialShapes = setToArray(getSelectedIds(data)) - .flatMap((id) => getDocumentBranch(data, id).map((id) => page.shapes[id])) + const initialShapes = setToArray(tld.getSelectedIds(data)) + .flatMap((id) => + tld.getDocumentBranch(data, id).map((id) => page.shapes[id]) + ) .filter((shape) => !shape.isLocked) .map((shape) => deepClone(shape)) diff --git a/state/sessions/transform-single-session.ts b/state/sessions/transform-single-session.ts index 169f50df2..b5d7db5dc 100644 --- a/state/sessions/transform-single-session.ts +++ b/state/sessions/transform-single-session.ts @@ -4,13 +4,8 @@ import BaseSession from './base-session' import commands from 'state/commands' import { current } from 'immer' import { getShapeUtils } from 'state/shape-utils' -import { - getTransformedBoundingBox, - getPage, - getShape, - getSelectedShapes, - updateParents, -} from 'utils' +import { getTransformedBoundingBox } from 'utils' +import tld from 'utils/tld' export default class TransformSingleSession extends BaseSession { transformType: Edge | Corner @@ -38,7 +33,7 @@ export default class TransformSingleSession extends BaseSession { const { initialShapeBounds, initialShape, id } = this.snapshot - const shape = getShape(data, id) + const shape = tld.getShape(data, id) const newBoundingBox = getTransformedBoundingBox( initialShapeBounds, @@ -61,16 +56,16 @@ export default class TransformSingleSession extends BaseSession { data.document.pages[data.currentPageId].shapes[shape.id] = { ...shape } - updateParents(data, [id]) + tld.updateParents(data, [id]) } cancel(data: Data): void { const { id, initialShape } = this.snapshot - const page = getPage(data) + const page = tld.getPage(data) page.shapes[id] = initialShape - updateParents(data, [id]) + tld.updateParents(data, [id]) } complete(data: Data): void { @@ -90,7 +85,7 @@ export function getTransformSingleSnapshot( data: Data, transformType: Edge | Corner ) { - const shape = getSelectedShapes(current(data))[0] + const shape = tld.getSelectedShapes(current(data))[0] const bounds = getShapeUtils(shape).getBounds(shape) return { diff --git a/state/sessions/translate-session.ts b/state/sessions/translate-session.ts index 8f526a82f..6edfbbd65 100644 --- a/state/sessions/translate-session.ts +++ b/state/sessions/translate-session.ts @@ -4,15 +4,8 @@ import BaseSession from './base-session' import commands from 'state/commands' import { current } from 'immer' import { uniqueId } from 'utils' -import { - getChildIndexAbove, - getDocumentBranch, - getPage, - getSelectedShapes, - setSelectedIds, - updateParents, -} from 'utils' import { getShapeUtils } from 'state/shape-utils' +import tld from 'utils/tld' export default class TranslateSession extends BaseSession { delta = [0, 0] @@ -34,7 +27,7 @@ export default class TranslateSession extends BaseSession { isCloning: boolean ): void { const { clones, initialShapes, initialParents } = this.snapshot - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) const delta = vec.vec(this.origin, point) @@ -89,12 +82,12 @@ export default class TranslateSession extends BaseSession { shapes[id] = { ...shape } } - setSelectedIds( + tld.setSelectedIds( data, clones.map((c) => c.id) ) - updateParents( + tld.updateParents( data, clones.map((c) => c.id) ) @@ -102,7 +95,7 @@ export default class TranslateSession extends BaseSession { if (this.isCloning) { this.isCloning = false - setSelectedIds( + tld.setSelectedIds( data, initialShapes.map((c) => c.id) ) @@ -112,7 +105,7 @@ export default class TranslateSession extends BaseSession { } for (const initialShape of initialShapes) { - getDocumentBranch(data, initialShape.id).forEach((id) => { + tld.getDocumentBranch(data, initialShape.id).forEach((id) => { const shape = shapes[id] getShapeUtils(shape).translateBy(shape, delta) shapes[id] = { ...shape } @@ -126,7 +119,7 @@ export default class TranslateSession extends BaseSession { } for (const initialShape of initialShapes) { - getDocumentBranch(data, initialShape.id).forEach((id) => { + tld.getDocumentBranch(data, initialShape.id).forEach((id) => { const shape = shapes[id] getShapeUtils(shape).translateBy(shape, trueDelta) @@ -134,7 +127,7 @@ export default class TranslateSession extends BaseSession { }) } - updateParents( + tld.updateParents( data, initialShapes.map((s) => s.id) ) @@ -143,10 +136,10 @@ export default class TranslateSession extends BaseSession { cancel(data: Data): void { const { initialShapes, initialParents, clones } = this.snapshot - const { shapes } = getPage(data) + const { shapes } = tld.getPage(data) for (const { id } of initialShapes) { - getDocumentBranch(data, id).forEach((id) => { + tld.getDocumentBranch(data, id).forEach((id) => { const shape = shapes[id] getShapeUtils(shape).translateBy(shape, vec.neg(this.delta)) }) @@ -161,7 +154,7 @@ export default class TranslateSession extends BaseSession { getShapeUtils(shape).setProperty(shape, 'children', children) }) - updateParents( + tld.updateParents( data, initialShapes.map((s) => s.id) ) @@ -190,10 +183,10 @@ export function getTranslateSnapshot(data: Data) { // End up with an array of ids for all of the shapes that will change // Map into shapes from data snapshot - const page = getPage(cData) - const selectedShapes = getSelectedShapes(cData).filter( - (shape) => !shape.isLocked - ) + const page = tld.getPage(cData) + const selectedShapes = tld + .getSelectedShapes(cData) + .filter((shape) => !shape.isLocked) const hasUnlockedShapes = selectedShapes.length > 0 @@ -220,7 +213,7 @@ export function getTranslateSnapshot(data: Data) { id: uniqueId(), parentId: shape.parentId, - childIndex: getChildIndexAbove(cData, shape.id), + childIndex: tld.getChildIndexAbove(cData, shape.id), isGenerated: false, } @@ -236,7 +229,7 @@ export type TranslateSnapshot = ReturnType // return [clone] // } -// const page = getPage(data) +// const page = tld.getPage(data) // const childClones = clone.children.flatMap((id) => { // const newId = uniqueId() // const source = page.shapes[id] diff --git a/state/state.ts b/state/state.ts index cefde5f41..4ca80f2c5 100644 --- a/state/state.ts +++ b/state/state.ts @@ -7,31 +7,18 @@ import history from './history' import storage from './storage' import clipboard from './clipboard' import * as Sessions from './sessions' -import pusher from './pusher/client' +import pusher from './pusher/client-supa' import commands from './commands' import { - getChildren, getCommonBounds, - getCurrentCamera, - getPage, - getSelectedBounds, - getSelectedShapes, - getShape, - screenToWorld, - setZoomCSS, rotateBounds, getBoundsCenter, - getDocumentBranch, - getCameraZoom, - getSelectedIds, - setSelectedIds, - getPageState, setToArray, deepClone, pointInBounds, uniqueId, } from 'utils' - +import tld from 'utils/tld' import { Data, PointerInfo, @@ -49,6 +36,7 @@ import { SizeStyle, ColorStyle, } from 'types' + import session from './session' const initialData: Data = { @@ -1041,10 +1029,10 @@ const state = createState({ return ShapeType.Rectangle }, firstSelectedShape(data) { - return getSelectedShapes(data)[0] + return tld.getSelectedShapes(data)[0] }, editingShape(data) { - return getShape(data, data.editingId) + return tld.getShape(data, data.editingId) }, }, conditions: { @@ -1058,7 +1046,7 @@ const state = createState({ return payload.target === 'canvas' }, isPointingBounds(data, payload: PointerInfo) { - return getSelectedIds(data).size > 0 && payload.target === 'bounds' + return tld.getSelectedIds(data).size > 0 && payload.target === 'bounds' }, isPointingShape(data, payload: PointerInfo) { return ( @@ -1083,7 +1071,7 @@ const state = createState({ return payload.target !== undefined }, isPointedShapeSelected(data) { - return getSelectedIds(data).has(data.pointedId) + return tld.getSelectedIds(data).has(data.pointedId) }, isPressingShiftKey(data, payload: PointerInfo) { return payload.shiftKey @@ -1099,18 +1087,18 @@ const state = createState({ if (!bounds) return false - return pointInBounds(screenToWorld(payload.point, data), bounds) + return pointInBounds(tld.screenToWorld(payload.point, data), bounds) }, pointHitsShape(data, payload: PointerInfo) { - const shape = getShape(data, payload.target) + const shape = tld.getShape(data, payload.target) return getShapeUtils(shape).hitTest( shape, - screenToWorld(payload.point, data) + tld.screenToWorld(payload.point, data) ) }, hasPointedId(data, payload: PointerInfo) { - return getShape(data, payload.target) !== undefined + return tld.getShape(data, payload.target) !== undefined }, isPointingRotationHandle( data, @@ -1119,13 +1107,13 @@ const state = createState({ return payload.target === 'rotate' }, hasSelection(data) { - return getSelectedIds(data).size > 0 + return tld.getSelectedIds(data).size > 0 }, hasSingleSelection(data) { - return getSelectedIds(data).size === 1 + return tld.getSelectedIds(data).size === 1 }, hasMultipleSelection(data) { - return getSelectedIds(data).size > 1 + return tld.getSelectedIds(data).size > 1 }, isToolLocked(data) { return data.settings.isToolLocked @@ -1137,9 +1125,9 @@ const state = createState({ return Object.keys(data.document.pages).length === 1 }, selectionIncludesGroups(data) { - return getSelectedShapes(data).some( - (shape) => shape.type === ShapeType.Group - ) + return tld + .getSelectedShapes(data) + .some((shape) => shape.type === ShapeType.Group) }, }, actions: { @@ -1174,7 +1162,7 @@ const state = createState({ Object.assign(data.document[pageId].shapes[shape.id], shape) }, sendRtCursorMove(data, payload: PointerInfo) { - const point = screenToWorld(payload.point, data) + const point = tld.screenToWorld(payload.point, data) pusher.moveCursor(data.currentPageId, point) }, moveRtCursor( @@ -1250,7 +1238,7 @@ const state = createState({ }, /* --------------------- Shapes --------------------- */ resetShapes(data) { - const page = getPage(data) + const page = tld.getPage(data) Object.values(page.shapes).forEach((shape) => { page.shapes[shape.id] = { ...shape } }) @@ -1259,11 +1247,11 @@ const state = createState({ createShape(data, payload, type: ShapeType) { const shape = createShape(type, { parentId: data.currentPageId, - point: vec.round(screenToWorld(payload.point, data)), + point: vec.round(tld.screenToWorld(payload.point, data)), style: deepClone(data.currentStyle), }) - const siblings = getChildren(data, shape.parentId) + const siblings = tld.getChildren(data, shape.parentId) const childIndex = siblings.length ? siblings[siblings.length - 1].childIndex + 1 : 1 @@ -1272,9 +1260,9 @@ const state = createState({ getShapeUtils(shape).setProperty(shape, 'childIndex', childIndex) - getPage(data).shapes[shape.id] = shape + tld.getPage(data).shapes[shape.id] = shape - setSelectedIds(data, [shape.id]) + tld.setSelectedIds(data, [shape.id]) }, /* -------------------- Sessions -------------------- */ @@ -1303,33 +1291,33 @@ const state = createState({ // Brushing startBrushSession(data, payload: PointerInfo) { session.begin( - new Sessions.BrushSession(data, screenToWorld(payload.point, data)) + new Sessions.BrushSession(data, tld.screenToWorld(payload.point, data)) ) }, updateBrushSession(data, payload: PointerInfo) { session.update( data, - screenToWorld(payload.point, data) + tld.screenToWorld(payload.point, data) ) }, // Rotating startRotateSession(data, payload: PointerInfo) { session.begin( - new Sessions.RotateSession(data, screenToWorld(payload.point, data)) + new Sessions.RotateSession(data, tld.screenToWorld(payload.point, data)) ) }, keyUpdateRotateSession(data, payload: PointerInfo) { session.update( data, - screenToWorld(inputs.pointer.point, data), + tld.screenToWorld(inputs.pointer.point, data), payload.shiftKey ) }, updateRotateSession(data, payload: PointerInfo) { session.update( data, - screenToWorld(payload.point, data), + tld.screenToWorld(payload.point, data), payload.shiftKey ) }, @@ -1339,7 +1327,7 @@ const state = createState({ session.begin( new Sessions.TranslateSession( data, - screenToWorld(inputs.pointer.origin, data) + tld.screenToWorld(inputs.pointer.origin, data) ) ) }, @@ -1349,7 +1337,7 @@ const state = createState({ ) { session.update( data, - screenToWorld(inputs.pointer.point, data), + tld.screenToWorld(inputs.pointer.point, data), payload.shiftKey, payload.altKey ) @@ -1357,7 +1345,7 @@ const state = createState({ updateTranslateSession(data, payload: PointerInfo) { session.update( data, - screenToWorld(payload.point, data), + tld.screenToWorld(payload.point, data), payload.shiftKey, payload.altKey ) @@ -1365,13 +1353,13 @@ const state = createState({ // Handles doublePointHandle(data, payload: PointerInfo) { - const id = setToArray(getSelectedIds(data))[0] + const id = setToArray(tld.getSelectedIds(data))[0] commands.doublePointHandle(data, id, payload) }, // Dragging Handle startHandleSession(data, payload: PointerInfo) { - const shapeId = Array.from(getSelectedIds(data).values())[0] + const shapeId = Array.from(tld.getSelectedIds(data).values())[0] const handleId = payload.target session.begin( @@ -1379,7 +1367,7 @@ const state = createState({ data, shapeId, handleId, - screenToWorld(inputs.pointer.origin, data) + tld.screenToWorld(inputs.pointer.origin, data) ) ) }, @@ -1389,14 +1377,14 @@ const state = createState({ ) { session.update( data, - screenToWorld(inputs.pointer.point, data), + tld.screenToWorld(inputs.pointer.point, data), payload.shiftKey ) }, updateHandleSession(data, payload: PointerInfo) { session.update( data, - screenToWorld(payload.point, data), + tld.screenToWorld(payload.point, data), payload.shiftKey ) }, @@ -1406,9 +1394,9 @@ const state = createState({ data, payload: PointerInfo & { target: Corner | Edge } ) { - const point = screenToWorld(inputs.pointer.origin, data) + const point = tld.screenToWorld(inputs.pointer.origin, data) session.begin( - getSelectedIds(data).size === 1 + tld.getSelectedIds(data).size === 1 ? new Sessions.TransformSingleSession(data, payload.target, point) : new Sessions.TransformSession(data, payload.target, point) ) @@ -1418,7 +1406,7 @@ const state = createState({ new Sessions.TransformSingleSession( data, Corner.BottomRight, - screenToWorld(payload.point, data), + tld.screenToWorld(payload.point, data), true ) ) @@ -1426,14 +1414,14 @@ const state = createState({ keyUpdateTransformSession(data, payload: PointerInfo) { session.update( data, - screenToWorld(inputs.pointer.point, data), + tld.screenToWorld(inputs.pointer.point, data), payload.shiftKey ) }, updateTransformSession(data, payload: PointerInfo) { session.update( data, - screenToWorld(payload.point, data), + tld.screenToWorld(payload.point, data), payload.shiftKey ) }, @@ -1443,32 +1431,32 @@ const state = createState({ session.begin( new Sessions.DirectionSession( data, - screenToWorld(inputs.pointer.origin, data) + tld.screenToWorld(inputs.pointer.origin, data) ) ) }, updateDirectionSession(data, payload: PointerInfo) { session.update( data, - screenToWorld(payload.point, data) + tld.screenToWorld(payload.point, data) ) }, // Drawing startDrawSession(data) { - const id = Array.from(getSelectedIds(data).values())[0] + const id = Array.from(tld.getSelectedIds(data).values())[0] session.begin( new Sessions.DrawSession( data, id, - screenToWorld(inputs.pointer.origin, data) + tld.screenToWorld(inputs.pointer.origin, data) ) ) }, keyUpdateDrawSession(data, payload: PointerInfo) { session.update( data, - screenToWorld(inputs.pointer.point, data), + tld.screenToWorld(inputs.pointer.point, data), payload.pressure, payload.shiftKey ) @@ -1476,7 +1464,7 @@ const state = createState({ updateDrawSession(data, payload: PointerInfo) { session.update( data, - screenToWorld(payload.point, data), + tld.screenToWorld(payload.point, data), payload.pressure, payload.shiftKey ) @@ -1484,13 +1472,13 @@ const state = createState({ // Arrow startArrowSession(data, payload: PointerInfo) { - const id = Array.from(getSelectedIds(data).values())[0] + const id = Array.from(tld.getSelectedIds(data).values())[0] session.begin( new Sessions.ArrowSession( data, id, - screenToWorld(inputs.pointer.origin, data), + tld.screenToWorld(inputs.pointer.origin, data), payload.shiftKey ) ) @@ -1498,14 +1486,14 @@ const state = createState({ keyUpdateArrowSession(data, payload: PointerInfo) { session.update( data, - screenToWorld(inputs.pointer.point, data), + tld.screenToWorld(inputs.pointer.point, data), payload.shiftKey ) }, updateArrowSession(data, payload: PointerInfo) { session.update( data, - screenToWorld(payload.point, data), + tld.screenToWorld(payload.point, data), payload.shiftKey ) }, @@ -1530,8 +1518,8 @@ const state = createState({ }, selectAll(data) { - const selectedIds = getSelectedIds(data) - const page = getPage(data) + const selectedIds = tld.getSelectedIds(data) + const page = tld.getPage(data) selectedIds.clear() for (const id in page.shapes) { if (page.shapes[id].parentId === data.currentPageId) { @@ -1561,15 +1549,15 @@ const state = createState({ data.pointedId = undefined }, clearSelectedIds(data) { - setSelectedIds(data, []) + tld.setSelectedIds(data, []) }, pullPointedIdFromSelectedIds(data) { const { pointedId } = data - const selectedIds = getSelectedIds(data) + const selectedIds = tld.getSelectedIds(data) selectedIds.delete(pointedId) }, pushPointedIdToSelectedIds(data) { - getSelectedIds(data).add(data.pointedId) + tld.getSelectedIds(data).add(data.pointedId) }, moveSelection(data, payload: { type: MoveType }) { commands.move(data, payload.type) @@ -1620,12 +1608,12 @@ const state = createState({ /* --------------------- Editing -------------------- */ setEditingId(data) { - const selectedShape = getSelectedShapes(data)[0] + const selectedShape = tld.getSelectedShapes(data)[0] if (getShapeUtils(selectedShape).canEdit) { data.editingId = selectedShape.id } - getPageState(data).selectedIds = new Set([selectedShape.id]) + tld.getPageState(data).selectedIds = new Set([selectedShape.id]) }, clearEditingId(data) { data.editingId = null @@ -1670,44 +1658,43 @@ const state = createState({ /* --------------------- Camera --------------------- */ zoomIn(data) { - const camera = getCurrentCamera(data) + const camera = tld.getCurrentCamera(data) const i = Math.round((camera.zoom * 100) / 25) const center = [window.innerWidth / 2, window.innerHeight / 2] - const p0 = screenToWorld(center, data) - camera.zoom = getCameraZoom((i + 1) * 0.25) - const p1 = screenToWorld(center, data) + const p0 = tld.screenToWorld(center, data) + camera.zoom = tld.getCameraZoom((i + 1) * 0.25) + const p1 = tld.screenToWorld(center, data) camera.point = vec.add(camera.point, vec.sub(p1, p0)) - setZoomCSS(camera.zoom) + tld.setZoomCSS(camera.zoom) }, zoomOut(data) { - const camera = getCurrentCamera(data) + const camera = tld.getCurrentCamera(data) const i = Math.round((camera.zoom * 100) / 25) const center = [window.innerWidth / 2, window.innerHeight / 2] - const p0 = screenToWorld(center, data) - camera.zoom = getCameraZoom((i - 1) * 0.25) - const p1 = screenToWorld(center, data) + const p0 = tld.screenToWorld(center, data) + camera.zoom = tld.getCameraZoom((i - 1) * 0.25) + const p1 = tld.screenToWorld(center, data) camera.point = vec.add(camera.point, vec.sub(p1, p0)) - setZoomCSS(camera.zoom) + tld.setZoomCSS(camera.zoom) }, zoomCameraToActual(data) { - const camera = getCurrentCamera(data) + const camera = tld.getCurrentCamera(data) const center = [window.innerWidth / 2, window.innerHeight / 2] - const p0 = screenToWorld(center, data) + const p0 = tld.screenToWorld(center, data) camera.zoom = 1 - const p1 = screenToWorld(center, data) + const p1 = tld.screenToWorld(center, data) camera.point = vec.add(camera.point, vec.sub(p1, p0)) - setZoomCSS(camera.zoom) + tld.setZoomCSS(camera.zoom) }, zoomCameraToSelectionActual(data) { - const camera = getCurrentCamera(data) - - const bounds = getSelectedBounds(data) + const camera = tld.getCurrentCamera(data) + const bounds = tld.getSelectedBounds(data) const mx = (window.innerWidth - bounds.width) / 2 const my = (window.innerHeight - bounds.height) / 2 @@ -1715,13 +1702,13 @@ const state = createState({ camera.zoom = 1 camera.point = vec.add([-bounds.minX, -bounds.minY], [mx, my]) - setZoomCSS(camera.zoom) + tld.setZoomCSS(camera.zoom) }, zoomCameraToSelection(data) { - const camera = getCurrentCamera(data) - const bounds = getSelectedBounds(data) + const camera = tld.getCurrentCamera(data) + const bounds = tld.getSelectedBounds(data) - const zoom = getCameraZoom( + const zoom = tld.getCameraZoom( bounds.width > bounds.height ? (window.innerWidth - 128) / bounds.width : (window.innerHeight - 128) / bounds.height @@ -1733,11 +1720,11 @@ const state = createState({ camera.zoom = zoom camera.point = vec.add([-bounds.minX, -bounds.minY], [mx, my]) - setZoomCSS(camera.zoom) + tld.setZoomCSS(camera.zoom) }, zoomCameraToFit(data) { - const camera = getCurrentCamera(data) - const page = getPage(data) + const camera = tld.getCurrentCamera(data) + const page = tld.getPage(data) const shapes = Object.values(page.shapes) @@ -1751,7 +1738,7 @@ const state = createState({ ) ) - const zoom = getCameraZoom( + const zoom = tld.getCameraZoom( bounds.width > bounds.height ? (window.innerWidth - 128) / bounds.width : (window.innerHeight - 128) / bounds.height @@ -1763,26 +1750,26 @@ const state = createState({ camera.zoom = zoom camera.point = vec.add([-bounds.minX, -bounds.minY], [mx, my]) - setZoomCSS(camera.zoom) + tld.setZoomCSS(camera.zoom) }, zoomCamera(data, payload: { delta: number; point: number[] }) { - const camera = getCurrentCamera(data) + const camera = tld.getCurrentCamera(data) const next = camera.zoom - (payload.delta / 100) * camera.zoom - const p0 = screenToWorld(payload.point, data) - camera.zoom = getCameraZoom(next) - const p1 = screenToWorld(payload.point, data) + const p0 = tld.screenToWorld(payload.point, data) + camera.zoom = tld.getCameraZoom(next) + const p1 = tld.screenToWorld(payload.point, data) camera.point = vec.add(camera.point, vec.sub(p1, p0)) - setZoomCSS(camera.zoom) + tld.setZoomCSS(camera.zoom) }, panCamera(data, payload: { delta: number[] }) { - const camera = getCurrentCamera(data) + const camera = tld.getCurrentCamera(data) camera.point = vec.sub(camera.point, vec.div(payload.delta, camera.zoom)) }, updateZoomCSS(data) { - const camera = getCurrentCamera(data) - setZoomCSS(camera.zoom) + const camera = tld.getCurrentCamera(data) + tld.setZoomCSS(camera.zoom) }, pinchCamera( data, @@ -1795,20 +1782,20 @@ const state = createState({ ) { // This is usually replaced with hacks.fastPinchCamera! - const camera = getCurrentCamera(data) + const camera = tld.getCurrentCamera(data) camera.point = vec.sub(camera.point, vec.div(payload.delta, camera.zoom)) const next = camera.zoom - (payload.distanceDelta / 300) * camera.zoom - const p0 = screenToWorld(payload.point, data) - camera.zoom = getCameraZoom(next) - const p1 = screenToWorld(payload.point, data) + const p0 = tld.screenToWorld(payload.point, data) + camera.zoom = tld.getCameraZoom(next) + const p1 = tld.screenToWorld(payload.point, data) camera.point = vec.add(camera.point, vec.sub(p1, p0)) - setZoomCSS(camera.zoom) + tld.setZoomCSS(camera.zoom) }, resetCamera(data) { - const camera = getCurrentCamera(data) + const camera = tld.getCurrentCamera(data) camera.zoom = 1 camera.point = [window.innerWidth / 2, window.innerHeight / 2] document.documentElement.style.setProperty('--camera-zoom', '1') @@ -1869,7 +1856,7 @@ const state = createState({ commands.generate(data, payload.shapes) }, updateGeneratedShapes(data, payload, result: { shapes: Shape[] }) { - setSelectedIds(data, []) + tld.setSelectedIds(data, []) history.disable() @@ -1913,7 +1900,7 @@ const state = createState({ }, copyToClipboard(data) { - clipboard.copy(getSelectedShapes(data)) + clipboard.copy(tld.getSelectedShapes(data)) }, copyStateToClipboard(data) { @@ -1986,26 +1973,26 @@ const state = createState({ }, values: { selectedIds(data) { - return setToArray(getSelectedIds(data)) + return setToArray(tld.getSelectedIds(data)) }, selectedBounds(data) { return getSelectionBounds(data) }, currentShapes(data) { - const page = getPage(data) + const page = tld.getPage(data) return Object.values(page.shapes) .filter((shape) => shape.parentId === page.id) .sort((a, b) => a.childIndex - b.childIndex) }, selectedStyle(data) { - const selectedIds = setToArray(getSelectedIds(data)) + const selectedIds = setToArray(tld.getSelectedIds(data)) const { currentStyle } = data if (selectedIds.length === 0) { return currentStyle } - const page = getPage(data) + const page = tld.getPage(data) const shapeStyles = selectedIds.map((id) => page.shapes[id].style) @@ -2036,12 +2023,12 @@ export default state export const useSelector = createSelectorHook(state) function getParentId(data: Data, id: string) { - const shape = getPage(data).shapes[id] + const shape = tld.getPage(data).shapes[id] return shape.parentId } function getPointedId(data: Data, id: string) { - const shape = getPage(data).shapes[id] + const shape = tld.getPage(data).shapes[id] if (!shape) return id return shape.parentId === data.currentParentId || @@ -2051,7 +2038,7 @@ function getPointedId(data: Data, id: string) { } function getDrilledPointedId(data: Data, id: string) { - const shape = getPage(data).shapes[id] + const shape = tld.getPage(data).shapes[id] return shape.parentId === data.currentPageId || shape.parentId === data.pointedId || shape.parentId === data.currentParentId @@ -2076,18 +2063,18 @@ function getDrilledPointedId(data: Data, id: string) { // } function getSelectionBounds(data: Data) { - const selectedIds = getSelectedIds(data) + const selectedIds = tld.getSelectedIds(data) - const page = getPage(data) + const page = tld.getPage(data) - const shapes = getSelectedShapes(data) + const shapes = tld.getSelectedShapes(data) if (selectedIds.size === 0) return null if (selectedIds.size === 1) { if (!shapes[0]) { - console.error('Could not find that shape! Clearing selected IDs.') - setSelectedIds(data, []) + console.warn('Could not find that shape! Clearing selected IDs.') + tld.setSelectedIds(data, []) return null } @@ -2120,7 +2107,7 @@ function getSelectionBounds(data: Data) { const uniqueSelectedShapeIds: string[] = Array.from( new Set( Array.from(selectedIds.values()).flatMap((id) => - getDocumentBranch(data, id) + tld.getDocumentBranch(data, id) ) ).values() ) diff --git a/types.ts b/types.ts index db1381281..1e3405f31 100644 --- a/types.ts +++ b/types.ts @@ -202,43 +202,39 @@ export interface GroupShape extends BaseShape { size: number[] } -// type DeepPartial = { -// [P in keyof T]?: DeepPartial -// } - export type ShapeProps = { [P in keyof T]?: P extends 'style' ? Partial : T[P] } -export type MutableShape = - | DotShape - | EllipseShape - | LineShape - | RayShape - | PolylineShape - | DrawShape - | RectangleShape - | ArrowShape - | TextShape - | GroupShape - -export interface Shapes { - [ShapeType.Dot]: Readonly - [ShapeType.Ellipse]: Readonly - [ShapeType.Line]: Readonly - [ShapeType.Ray]: Readonly - [ShapeType.Polyline]: Readonly - [ShapeType.Draw]: Readonly - [ShapeType.Rectangle]: Readonly - [ShapeType.Arrow]: Readonly - [ShapeType.Text]: Readonly - [ShapeType.Group]: Readonly +export interface MutableShapes { + [ShapeType.Dot]: DotShape + [ShapeType.Ellipse]: EllipseShape + [ShapeType.Line]: LineShape + [ShapeType.Ray]: RayShape + [ShapeType.Polyline]: PolylineShape + [ShapeType.Draw]: DrawShape + [ShapeType.Rectangle]: RectangleShape + [ShapeType.Arrow]: ArrowShape + [ShapeType.Text]: TextShape + [ShapeType.Group]: GroupShape } +export type MutableShape = MutableShapes[keyof MutableShapes] + +export type Shapes = { [K in keyof MutableShapes]: Readonly } + export type Shape = Readonly export type ShapeByType = Shapes[T] +export type IsParent = 'children' extends RequiredKeys ? T : never + +export type ParentShape = { + [K in keyof MutableShapes]: IsParent +}[keyof MutableShapes] + +export type ParentTypes = ParentShape['type'] & 'page' + export enum Decoration { Arrow = 'Arrow', } @@ -333,8 +329,6 @@ export interface BoundsSnapshot extends PointSnapshot { nh: number } -export type Difference = A extends B ? never : A - export type ShapeSpecificProps = Pick< T, Difference @@ -617,3 +611,13 @@ export interface ShapeUtility { // Get whether the shape should render shouldRender(this: ShapeUtility, shape: K, previous: K): boolean } + +/* -------------------------------------------------- */ +/* Utilities */ +/* -------------------------------------------------- */ + +export type Difference = A extends B ? never : A + +export type RequiredKeys = { + [K in keyof T]-?: Record extends Pick ? never : K +}[keyof T] diff --git a/utils/tld.ts b/utils/tld.ts new file mode 100644 index 000000000..b653593ea --- /dev/null +++ b/utils/tld.ts @@ -0,0 +1,490 @@ +import { clamp, deepClone, getCommonBounds, setToArray } from './utils' +import { getShapeUtils } from 'state/shape-utils' +import vec from './vec' +import { + Data, + Bounds, + Shape, + GroupShape, + ShapeType, + CodeFile, + Page, + PageState, + ShapeUtility, + ParentShape, +} from 'types' +import { AssertionError } from 'assert' + +export default class ProjectUtils { + static getCameraZoom(zoom: number): number { + return clamp(zoom, 0.1, 5) + } + + static screenToWorld(point: number[], data: Data): number[] { + const camera = this.getCurrentCamera(data) + return vec.sub(vec.div(point, camera.zoom), camera.point) + } + + static getViewport(data: Data): Bounds { + const [minX, minY] = this.screenToWorld([0, 0], data) + const [maxX, maxY] = this.screenToWorld( + [window.innerWidth, window.innerHeight], + data + ) + + return { + minX, + minY, + maxX, + maxY, + height: maxX - minX, + width: maxY - minY, + } + } + + static getCurrentCamera(data: Data): { + point: number[] + zoom: number + } { + return data.pageStates[data.currentPageId].camera + } + + /** + * Get a shape from the project. + * @param data + * @param shapeId + */ + static getShape(data: Data, shapeId: string): Shape { + return data.document.pages[data.currentPageId].shapes[shapeId] + } + + /** + * Get the current page. + * @param data + */ + static getPage(data: Data): Page { + return data.document.pages[data.currentPageId] + } + + /** + * Get the current page's page state. + * @param data + */ + static getPageState(data: Data): PageState { + return data.pageStates[data.currentPageId] + } + + /** + * Get the current page's code file. + * @param data + * @param fileId + */ + static getCurrentCode(data: Data, fileId: string): CodeFile { + return data.document.code[fileId] + } + + /** + * Get the current page's shapes as an array. + * @param data + */ + static getShapes(data: Data): Shape[] { + const page = this.getPage(data) + return Object.values(page.shapes) + } + + /** + * Get the current selected shapes as an array. + * @param data + */ + static getSelectedShapes(data: Data): Shape[] { + const page = this.getPage(data) + const ids = setToArray(this.getSelectedIds(data)) + return ids.map((id) => page.shapes[id]) + } + + /** + * Get a shape's parent. + * @param data + * @param id + */ + static getParent(data: Data, id: string): Shape | Page { + const page = this.getPage(data) + const shape = page.shapes[id] + + return page.shapes[shape.parentId] || data.document.pages[shape.parentId] + } + + /** + * Get a shape's children. + * @param data + * @param id + */ + static getChildren(data: Data, id: string): Shape[] { + const page = this.getPage(data) + return Object.values(page.shapes) + .filter(({ parentId }) => parentId === id) + .sort((a, b) => a.childIndex - b.childIndex) + } + + /** + * Get a shape's siblings. + * @param data + * @param id + */ + static getSiblings(data: Data, id: string): Shape[] { + const page = this.getPage(data) + const shape = page.shapes[id] + + return Object.values(page.shapes) + .filter(({ parentId }) => parentId === shape.parentId) + .sort((a, b) => a.childIndex - b.childIndex) + } + + /** + * Get the next child index above a shape. + * @param data + * @param id + */ + static getChildIndexAbove(data: Data, id: string): number { + const page = this.getPage(data) + + const shape = page.shapes[id] + + const siblings = Object.values(page.shapes) + .filter(({ parentId }) => parentId === shape.parentId) + .sort((a, b) => a.childIndex - b.childIndex) + + const index = siblings.indexOf(shape) + + const nextSibling = siblings[index + 1] + + if (!nextSibling) { + return shape.childIndex + 1 + } + + let nextIndex = (shape.childIndex + nextSibling.childIndex) / 2 + + if (nextIndex === nextSibling.childIndex) { + this.forceIntegerChildIndices(siblings) + nextIndex = (shape.childIndex + nextSibling.childIndex) / 2 + } + + return nextIndex + } + + /** + * Get the next child index below a shape. + * @param data + * @param id + * @param pageId + */ + static getChildIndexBelow(data: Data, id: string): number { + const page = this.getPage(data) + + const shape = page.shapes[id] + + const siblings = Object.values(page.shapes) + .filter(({ parentId }) => parentId === shape.parentId) + .sort((a, b) => a.childIndex - b.childIndex) + + const index = siblings.indexOf(shape) + + const prevSibling = siblings[index - 1] + + if (!prevSibling) { + return shape.childIndex / 2 + } + + let nextIndex = (shape.childIndex + prevSibling.childIndex) / 2 + + if (nextIndex === prevSibling.childIndex) { + this.forceIntegerChildIndices(siblings) + nextIndex = (shape.childIndex + prevSibling.childIndex) / 2 + } + + return (shape.childIndex + prevSibling.childIndex) / 2 + } + + /** + * Assert whether a shape can have child shapes. + * @param shape + */ + static assertParentShape(shape: Shape): asserts shape is ParentShape { + if (!('children' in shape)) { + throw new AssertionError({ + message: `That shape was not a parent (it was a ${shape.type}).`, + }) + } + } + + /** + * Get the top child index for a shape. This is potentially provisional: + * sorting all shapes on the page for each new created shape will become + * slower as the page grows. High indices aren't a problem, so consider + * tracking the highest index for the page when shapes are created / deleted. + * + * @param data + * @param id + */ + static getTopChildIndex(data: Data, parent: Shape | Page): number { + const page = this.getPage(data) + + // If the parent is a shape, return either 1 (if no other shapes) or the + // highest sorted child index + 1. + if (parent.type === 'page') { + const children = Object.values(parent.shapes) + + if (children.length === 0) return 1 + + return ( + children.sort((a, b) => b.childIndex - a.childIndex)[0].childIndex + 1 + ) + } + + // If the shape is a regular shape that can accept children, return either + // 1 (if no other children) or the highest sorted child index + 1. + this.assertParentShape(parent) + + if (parent.children.length === 0) return 1 + + return ( + parent.children + .map((id) => page.shapes[id]) + .sort((a, b) => b.childIndex - a.childIndex)[0].childIndex + 1 + ) + } + + /** + * TODO: Make this recursive, so that it works for parented shapes. + * Force all shapes on the page to have integer child indices. + * @param shapes + */ + static forceIntegerChildIndices(shapes: Shape[]): void { + for (let i = 0; i < shapes.length; i++) { + const shape = shapes[i] + getShapeUtils(shape).setProperty(shape, 'childIndex', i + 1) + } + } + + /** + * Update the zoom CSS variable. + * @param zoom ; + */ + static setZoomCSS(zoom: number): void { + document.documentElement.style.setProperty('--camera-zoom', zoom.toString()) + } + + /* --------------------- Groups --------------------- */ + + static getParentOffset( + data: Data, + shapeId: string, + offset = [0, 0] + ): number[] { + const shape = this.getShape(data, shapeId) + return shape.parentId === data.currentPageId + ? offset + : this.getParentOffset(data, shape.parentId, vec.add(offset, shape.point)) + } + + static getParentRotation(data: Data, shapeId: string, rotation = 0): number { + const shape = this.getShape(data, shapeId) + return shape.parentId === data.currentPageId + ? rotation + shape.rotation + : this.getParentRotation(data, shape.parentId, rotation + shape.rotation) + } + + static getDocumentBranch(data: Data, id: string): string[] { + const shape = this.getPage(data).shapes[id] + + if (shape.type !== ShapeType.Group) return [id] + + return [ + id, + ...shape.children.flatMap((childId) => + this.getDocumentBranch(data, childId) + ), + ] + } + + static getSelectedIds(data: Data): Set { + return data.pageStates[data.currentPageId].selectedIds + } + + static setSelectedIds(data: Data, ids: string[]): Set { + data.pageStates[data.currentPageId].selectedIds = new Set(ids) + return data.pageStates[data.currentPageId].selectedIds + } + + static getTopParentId(data: Data, id: string): string { + const shape = this.getPage(data).shapes[id] + return shape.parentId === data.currentPageId || + shape.parentId === data.currentParentId + ? id + : this.getTopParentId(data, shape.parentId) + } + + /* ----------------- Shapes Related ----------------- */ + + static getSelectedShapeSnapshot(data: Data): Shape[] + static getSelectedShapeSnapshot( + data: Data, + fn: (shape: T) => K + ): ({ id: string } & K)[] + static getSelectedShapeSnapshot< + K, + F extends (shape: T) => K + >(data: Data, fn?: F): (Shape | K)[] { + const copies = this.getSelectedShapes(data) + .filter((shape) => !shape.isLocked) + .map((shape) => deepClone(shape)) + + if (fn !== undefined) { + return copies.map((shape) => ({ id: shape.id, ...fn(shape) })) + } + + return copies + } + + /** + * Make an arbitrary change to shape. + * @param data + * @param ids + * @param fn + */ + static mutateShape( + data: Data, + id: string, + fn: (shapeUtils: ShapeUtility, shape: T) => void, + updateParents = true + ): T { + const page = this.getPage(data) + + const shape = page.shapes[id] as T + fn(getShapeUtils(shape) as ShapeUtility, shape) + + if (updateParents) this.updateParents(data, [id]) + + return shape + } + + /** + * Make an arbitrary change to a set of shapes. + * @param data + * @param ids + * @param fn + */ + static mutateShapes( + data: Data, + ids: string[], + fn: (shape: T, shapeUtils: ShapeUtility, index: number) => T | void, + updateParents = true + ): T[] { + const page = this.getPage(data) + + const mutatedShapes = ids.map((id, i) => { + const shape = page.shapes[id] as T + + // Define the new shape as either the (maybe new) shape returned by the + // function or the mutated shape. + page.shapes[id] = + fn(shape, getShapeUtils(shape) as ShapeUtility, i) || shape + + return page.shapes[id] as T + }) + + if (updateParents) this.updateParents(data, ids) + + return mutatedShapes + } + + /** + * Insert shapes into the current page. + * @param data + * @param shapes + */ + static insertShapes(data: Data, shapes: Shape[]): void { + const page = this.getPage(data) + + shapes.forEach((shape) => { + page.shapes[shape.id] = shape + + // Does the shape have a parent? + if (shape.parentId !== data.currentPageId) { + // The parent shape + const parent = page.shapes[shape.parentId] + + // If the parent shape doesn't exist, assign the shape as a child + // of the page instead. + if (parent === undefined) { + getShapeUtils(shape).setProperty( + shape, + 'childIndex', + this.getTopChildIndex(data, parent) + ) + } else { + // Add the shape's id to the parent's children, then sort the + // new array just to be sure. + getShapeUtils(parent).setProperty( + parent, + 'children', + [...parent.children, shape.id] + .map((id) => page.shapes[id]) + .sort((a, b) => a.childIndex - b.childIndex) + .map((shape) => shape.id) + ) + } + } + }) + + // Update any new parents + this.updateParents( + data, + shapes.map((shape) => shape.id) + ) + } + + static getRotatedBounds(shape: Shape): Bounds { + return getShapeUtils(shape).getRotatedBounds(shape) + } + + static getShapeBounds(shape: Shape): Bounds { + return getShapeUtils(shape).getBounds(shape) + } + + static getSelectedBounds(data: Data): Bounds { + return getCommonBounds( + ...this.getSelectedShapes(data).map((shape) => + getShapeUtils(shape).getBounds(shape) + ) + ) + } + + /** + * Recursively update shape parents. + * @param data + * @param changedShapeIds + */ + static updateParents(data: Data, changedShapeIds: string[]): void { + if (changedShapeIds.length === 0) return + + const { shapes } = this.getPage(data) + + const parentToUpdateIds = Array.from( + new Set(changedShapeIds.map((id) => shapes[id].parentId).values()) + ).filter((id) => id !== data.currentPageId) + + for (const parentId of parentToUpdateIds) { + const parent = shapes[parentId] as GroupShape + + getShapeUtils(parent).onChildrenChange( + parent, + parent.children.map((id) => shapes[id]) + ) + + shapes[parentId] = { ...parent } + } + + this.updateParents(data, parentToUpdateIds) + } +} diff --git a/utils/utils.ts b/utils/utils.ts index dfc50aa58..a8336db49 100644 --- a/utils/utils.ts +++ b/utils/utils.ts @@ -1,22 +1,9 @@ import React from 'react' -import { - Data, - Bounds, - Edge, - Corner, - Shape, - GroupShape, - ShapeType, - CodeFile, - Page, - PageState, - BezierCurveSegment, -} from 'types' +import { Bounds, Edge, Corner, BezierCurveSegment } from 'types' import { v4 as uuid } from 'uuid' import vec from './vec' import _isMobile from 'ismobilejs' import { intersectPolygonBounds } from './intersections' -import { getShapeUtils } from 'state/shape-utils' /* ----------- Numbers and Data Structures ---------- */ @@ -1789,304 +1776,3 @@ export function commandKey(): string { // return delta // } - -/* -------------------------------------------------- */ -/* State Utils */ -/* -------------------------------------------------- */ - -export function getCameraZoom(zoom: number): number { - return clamp(zoom, 0.1, 5) -} - -export function screenToWorld(point: number[], data: Data): number[] { - const camera = getCurrentCamera(data) - return vec.sub(vec.div(point, camera.zoom), camera.point) -} - -export function getViewport(data: Data): Bounds { - const [minX, minY] = screenToWorld([0, 0], data) - const [maxX, maxY] = screenToWorld( - [window.innerWidth, window.innerHeight], - data - ) - - return { - minX, - minY, - maxX, - maxY, - height: maxX - minX, - width: maxY - minY, - } -} - -export function getCurrentCamera(data: Data): { - point: number[] - zoom: number -} { - return data.pageStates[data.currentPageId].camera -} - -/** - * Get a shape from the project. - * @param data - * @param shapeId - */ -export function getShape(data: Data, shapeId: string): Shape { - return data.document.pages[data.currentPageId].shapes[shapeId] -} - -/** - * Get the current page. - * @param data - */ -export function getPage(data: Data): Page { - return data.document.pages[data.currentPageId] -} - -/** - * Get the current page's page state. - * @param data - */ -export function getPageState(data: Data): PageState { - return data.pageStates[data.currentPageId] -} - -/** - * Get the current page's code file. - * @param data - * @param fileId - */ -export function getCurrentCode(data: Data, fileId: string): CodeFile { - return data.document.code[fileId] -} - -/** - * Get the current page's shapes as an array. - * @param data - */ -export function getShapes(data: Data): Shape[] { - const page = getPage(data) - return Object.values(page.shapes) -} - -/** - * Get the current selected shapes as an array. - * @param data - */ -export function getSelectedShapes(data: Data): Shape[] { - const page = getPage(data) - const ids = setToArray(getSelectedIds(data)) - return ids.map((id) => page.shapes[id]) -} - -/** - * Get a shape's parent. - * @param data - * @param id - */ -export function getParent(data: Data, id: string): Shape | Page { - const page = getPage(data) - const shape = page.shapes[id] - - return page.shapes[shape.parentId] || data.document.pages[shape.parentId] -} - -/** - * Get a shape's children. - * @param data - * @param id - */ -export function getChildren(data: Data, id: string): Shape[] { - const page = getPage(data) - return Object.values(page.shapes) - .filter(({ parentId }) => parentId === id) - .sort((a, b) => a.childIndex - b.childIndex) -} - -/** - * Get a shape's siblings. - * @param data - * @param id - */ -export function getSiblings(data: Data, id: string): Shape[] { - const page = getPage(data) - const shape = page.shapes[id] - - return Object.values(page.shapes) - .filter(({ parentId }) => parentId === shape.parentId) - .sort((a, b) => a.childIndex - b.childIndex) -} - -/** - * Get the next child index above a shape. - * @param data - * @param id - */ -export function getChildIndexAbove(data: Data, id: string): number { - const page = getPage(data) - - const shape = page.shapes[id] - - const siblings = Object.values(page.shapes) - .filter(({ parentId }) => parentId === shape.parentId) - .sort((a, b) => a.childIndex - b.childIndex) - - const index = siblings.indexOf(shape) - - const nextSibling = siblings[index + 1] - - if (!nextSibling) { - return shape.childIndex + 1 - } - - let nextIndex = (shape.childIndex + nextSibling.childIndex) / 2 - - if (nextIndex === nextSibling.childIndex) { - forceIntegerChildIndices(siblings) - nextIndex = (shape.childIndex + nextSibling.childIndex) / 2 - } - - return nextIndex -} - -/** - * Get the next child index below a shape. - * @param data - * @param id - * @param pageId - */ -export function getChildIndexBelow(data: Data, id: string): number { - const page = getPage(data) - - const shape = page.shapes[id] - - const siblings = Object.values(page.shapes) - .filter(({ parentId }) => parentId === shape.parentId) - .sort((a, b) => a.childIndex - b.childIndex) - - const index = siblings.indexOf(shape) - - const prevSibling = siblings[index - 1] - - if (!prevSibling) { - return shape.childIndex / 2 - } - - let nextIndex = (shape.childIndex + prevSibling.childIndex) / 2 - - if (nextIndex === prevSibling.childIndex) { - forceIntegerChildIndices(siblings) - nextIndex = (shape.childIndex + prevSibling.childIndex) / 2 - } - - return (shape.childIndex + prevSibling.childIndex) / 2 -} - -export function forceIntegerChildIndices(shapes: Shape[]): void { - for (let i = 0; i < shapes.length; i++) { - const shape = shapes[i] - getShapeUtils(shape).setProperty(shape, 'childIndex', i + 1) - } -} - -/** - * Update the zoom CSS variable. - * @param zoom ; - */ -export function setZoomCSS(zoom: number): void { - document.documentElement.style.setProperty('--camera-zoom', zoom.toString()) -} - -/* --------------------- Groups --------------------- */ - -export function getParentOffset( - data: Data, - shapeId: string, - offset = [0, 0] -): number[] { - const shape = getShape(data, shapeId) - return shape.parentId === data.currentPageId - ? offset - : getParentOffset(data, shape.parentId, vec.add(offset, shape.point)) -} - -export function getParentRotation( - data: Data, - shapeId: string, - rotation = 0 -): number { - const shape = getShape(data, shapeId) - return shape.parentId === data.currentPageId - ? rotation + shape.rotation - : getParentRotation(data, shape.parentId, rotation + shape.rotation) -} - -export function getDocumentBranch(data: Data, id: string): string[] { - const shape = getPage(data).shapes[id] - - if (shape.type !== ShapeType.Group) return [id] - - return [ - id, - ...shape.children.flatMap((childId) => getDocumentBranch(data, childId)), - ] -} - -export function getSelectedIds(data: Data): Set { - return data.pageStates[data.currentPageId].selectedIds -} - -export function setSelectedIds(data: Data, ids: string[]): Set { - data.pageStates[data.currentPageId].selectedIds = new Set(ids) - return data.pageStates[data.currentPageId].selectedIds -} - -export function getTopParentId(data: Data, id: string): string { - const shape = getPage(data).shapes[id] - return shape.parentId === data.currentPageId || - shape.parentId === data.currentParentId - ? id - : getTopParentId(data, shape.parentId) -} - -/* ----------------- Shapes Related ----------------- */ - -export function getRotatedBounds(shape: Shape): Bounds { - return getShapeUtils(shape).getRotatedBounds(shape) -} - -export function getShapeBounds(shape: Shape): Bounds { - return getShapeUtils(shape).getBounds(shape) -} - -export function getSelectedBounds(data: Data): Bounds { - return getCommonBounds( - ...getSelectedShapes(data).map((shape) => - getShapeUtils(shape).getBounds(shape) - ) - ) -} - -export function updateParents(data: Data, changedShapeIds: string[]): void { - if (changedShapeIds.length === 0) return - - const { shapes } = getPage(data) - - const parentToUpdateIds = Array.from( - new Set(changedShapeIds.map((id) => shapes[id].parentId).values()) - ).filter((id) => id !== data.currentPageId) - - for (const parentId of parentToUpdateIds) { - const parent = shapes[parentId] as GroupShape - - getShapeUtils(parent).onChildrenChange( - parent, - parent.children.map((id) => shapes[id]) - ) - - shapes[parentId] = { ...parent } - } - - updateParents(data, parentToUpdateIds) -} diff --git a/yarn.lock b/yarn.lock index 7f001544c..443a8a6ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1924,6 +1924,14 @@ resolved "https://registry.yarnpkg.com/@stitches/react/-/react-0.2.2.tgz#8a2f8256835510c265ab20820f54a5bb0376bac6" integrity sha512-/qRSX+ANPJg/0eLNr5bEywjkdvIfLbsaG2d8p83wPlI0MA7Yi9FzaRLk2H6DMAdJHuyu6ThY4HfHQIL35buY9g== +"@supabase/realtime-js@^1.0.9": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@supabase/realtime-js/-/realtime-js-1.0.9.tgz#a4be4f893be78db4487ad2c9de155a4a07532eae" + integrity sha512-hGClyW7hHXW0PC6reJgaKFL0c3ubC+AVt7U/MxD0VJNjVXIw4PLj7DxgMpCIpNXksHJsLOBL8ht+BMhPb6rE8Q== + dependencies: + "@types/websocket" "^1.0.1" + websocket "^1.0.34" + "@surma/rollup-plugin-off-main-thread@^1.4.1": version "1.4.2" resolved "https://registry.yarnpkg.com/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-1.4.2.tgz#e6786b6af5799f82f7ab3a82e53f6182d2b91a58" @@ -2145,6 +2153,13 @@ anymatch "^3.0.0" source-map "^0.6.0" +"@types/websocket@^1.0.1": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@types/websocket/-/websocket-1.0.2.tgz#d2855c6a312b7da73ed16ba6781815bf30c6187a" + integrity sha512-B5m9aq7cbbD/5/jThEr33nUY8WEfVi6A2YKCTOvw5Ldy7mtsOkqRvGjnzy6g7iMMDsgu7xREuCzqATLDLQVKcQ== + dependencies: + "@types/node" "*" + "@types/yargs-parser@*": version "20.2.0" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.0.tgz#dd3e6699ba3237f0348cd085e4698780204842f9" @@ -2838,6 +2853,13 @@ buffer@^6.0.3: base64-js "^1.3.1" ieee754 "^1.2.1" +bufferutil@^4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.3.tgz#66724b756bed23cd7c28c4d306d7994f9943cc6b" + integrity sha512-yEYTwGndELGvfXsImMBLop58eaGW+YdONi1fNjTINSY98tmMmFijBG6WXgdkfuLNt4imzQNtIE+eBp1PVpMCSw== + dependencies: + node-gyp-build "^4.2.0" + builtin-modules@^3.1.0: version "3.2.0" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887" @@ -3286,6 +3308,14 @@ csstype@^3.0.2, csstype@^3.0.4: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.8.tgz#d2266a792729fb227cd216fb572f43728e1ad340" integrity sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw== +d@1, d@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" + integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== + dependencies: + es5-ext "^0.10.50" + type "^1.0.1" + damerau-levenshtein@^1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.7.tgz#64368003512a1a6992593741a09a9d31a836f55d" @@ -3305,7 +3335,7 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" -debug@2, debug@^2.6.9: +debug@2, debug@^2.2.0, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -3575,11 +3605,37 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +es5-ext@^0.10.35, es5-ext@^0.10.50: + version "0.10.53" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" + integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q== + dependencies: + es6-iterator "~2.0.3" + es6-symbol "~3.1.3" + next-tick "~1.0.0" + +es6-iterator@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" + integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= + dependencies: + d "1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" + es6-object-assign@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/es6-object-assign/-/es6-object-assign-1.1.0.tgz#c2c3582656247c39ea107cb1e6652b6f9f24523c" integrity sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw= +es6-symbol@^3.1.1, es6-symbol@~3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" + integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== + dependencies: + d "^1.0.1" + ext "^1.1.2" + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -3903,6 +3959,13 @@ expect@^27.0.2: jest-message-util "^27.0.2" jest-regex-util "^27.0.1" +ext@^1.1.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" + integrity sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A== + dependencies: + type "^2.0.0" + extend@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" @@ -5932,6 +5995,11 @@ next-pwa@^5.2.21: workbox-webpack-plugin "^6.1.5" workbox-window "^6.1.5" +next-tick@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" + integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= + next@^11.0.1: version "11.0.1" resolved "https://registry.yarnpkg.com/next/-/next-11.0.1.tgz#b8e3914d153aaf7143cb98c09bcd3c8230eeb17a" @@ -5993,6 +6061,11 @@ node-fetch@2.6.1, node-fetch@^2.6.0, node-fetch@^2.6.1: resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== +node-gyp-build@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.2.3.tgz#ce6277f853835f718829efb47db20f3e4d9c4739" + integrity sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg== + node-html-parser@1.4.9: version "1.4.9" resolved "https://registry.yarnpkg.com/node-html-parser/-/node-html-parser-1.4.9.tgz#3c8f6cac46479fae5800725edb532e9ae8fd816c" @@ -7899,6 +7972,16 @@ type-fest@^0.7.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.7.1.tgz#8dda65feaf03ed78f0a3f9678f1869147f7c5c48" integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg== +type@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" + integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== + +type@^2.0.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/type/-/type-2.5.0.tgz#0a2e78c2e77907b252abe5f298c1b01c63f0db3d" + integrity sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw== + typedarray-to-buffer@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" @@ -8085,6 +8168,13 @@ use-subscription@1.5.1: dependencies: object-assign "^4.1.1" +utf-8-validate@^5.0.2: + version "5.0.5" + resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.5.tgz#dd32c2e82c72002dc9f02eb67ba6761f43456ca1" + integrity sha512-+pnxRYsS/axEpkrrEpzYfNZGXp0IjC/9RIxwM5gntY4Koi8SHmUGSfxfWqxZdRxrtaoVstuOzUp/rbs3JSPELQ== + dependencies: + node-gyp-build "^4.2.0" + util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -8230,6 +8320,18 @@ webpack-sources@^1.4.3: source-list-map "^2.0.0" source-map "~0.6.1" +websocket@^1.0.34: + version "1.0.34" + resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.34.tgz#2bdc2602c08bf2c82253b730655c0ef7dcab3111" + integrity sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ== + dependencies: + bufferutil "^4.0.1" + debug "^2.2.0" + es5-ext "^0.10.50" + typedarray-to-buffer "^3.1.5" + utf-8-validate "^5.0.2" + yaeti "^0.0.6" + whatwg-encoding@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" @@ -8546,6 +8648,11 @@ y18n@^5.0.5: resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== +yaeti@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/yaeti/-/yaeti-0.0.6.tgz#f26f484d72684cf42bedfb76970aa1608fbf9577" + integrity sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc= + yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"