big cleanup

This commit is contained in:
Steve Ruiz 2021-06-21 22:35:28 +01:00
parent daa44f9911
commit 864ded959a
161 changed files with 2918 additions and 5749 deletions

View file

@ -1,7 +1,3 @@
{
"env": {
"test": {
"presets": ["next/babel"]
}
}
"presets": ["next/babel"]
}

3
.eslintignore Normal file
View file

@ -0,0 +1,3 @@
**/node_modules/*
**/out/*
**/.next/*

49
.eslintrc.json Normal file
View file

@ -0,0 +1,49 @@
{
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended"
// Uncomment the following lines to enable eslint-config-prettier
// Is not enabled right now to avoid issues with the Next.js repo
// "prettier",
],
"env": {
"es6": true,
"browser": true,
"jest": true,
"node": true
},
"settings": {
"react": {
"version": "detect"
}
},
"ignorePatterns": "**/*.js",
"rules": {
"react/react-in-jsx-scope": 0,
"react/display-name": 0,
"react/prop-types": 0,
"@typescript-eslint/no-extra-semi": 0,
"@typescript-eslint/explicit-function-return-type": 0,
"@typescript-eslint/explicit-member-accessibility": 0,
"@typescript-eslint/indent": 0,
"@typescript-eslint/member-delimiter-style": 0,
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/no-var-requires": 0,
"@typescript-eslint/no-use-before-define": 0,
"@typescript-eslint/no-unused-vars": [
2,
{
"argsIgnorePattern": "^_"
}
],
"no-console": [
2,
{
"allow": ["warn", "error"]
}
]
}
}

21
LICENSE
View file

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2021 Steve Ruiz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,34 +1,3 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
# tldraw
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
A tiny little drawing app by [steveruizok](https://twitter.com/steveruizok).

View file

@ -15,7 +15,7 @@ import CornerHandle from './corner-handle'
import EdgeHandle from './edge-handle'
import RotateHandle from './rotate-handle'
export default function Bounds() {
export default function Bounds(): JSX.Element {
const isBrushing = useSelector((s) => s.isIn('brushSelecting'))
const isSelecting = useSelector((s) => s.isIn('selecting'))

View file

@ -24,7 +24,7 @@ function handlePointerUp(e: React.PointerEvent<SVGRectElement>) {
state.send('STOPPED_POINTING', inputs.pointerUp(e))
}
export default function BoundsBg() {
export default function BoundsBg(): JSX.Element {
const rBounds = useRef<SVGRectElement>(null)
const bounds = useSelector((state) => state.values.selectedBounds)

View file

@ -7,7 +7,7 @@ export default function CenterHandle({
}: {
bounds: Bounds
isLocked: boolean
}) {
}): JSX.Element {
return (
<StyledBounds
x={-1}

View file

@ -10,7 +10,7 @@ export default function CornerHandle({
size: number
bounds: Bounds
corner: Corner
}) {
}): JSX.Element {
const events = useBoundsEvents(corner)
const isTop = corner === Corner.TopLeft || corner === Corner.TopRight

View file

@ -10,7 +10,7 @@ export default function EdgeHandle({
size: number
bounds: Bounds
edge: Edge
}) {
}): JSX.Element {
const events = useBoundsEvents(edge)
const isHorizontal = edge === Edge.Top || edge === Edge.Bottom

View file

@ -1,12 +1,12 @@
import useHandleEvents from 'hooks/useHandleEvents'
import { getShapeUtils } from 'lib/shape-utils'
import { getShapeUtils } from 'state/shape-utils'
import { useRef } from 'react'
import { useSelector } from 'state'
import styled from 'styles'
import { deepCompareArrays, getPage } from 'utils/utils'
import vec from 'utils/vec'
export default function Handles() {
export default function Handles(): JSX.Element {
const selectedIds = useSelector(
(s) => Array.from(s.values.selectedIds.values()),
deepCompareArrays

View file

@ -8,7 +8,7 @@ export default function Rotate({
}: {
bounds: Bounds
size: number
}) {
}): JSX.Element {
const events = useHandleEvents('rotate')
return (

View file

@ -1,7 +1,7 @@
import { useSelector } from "state"
import styled from "styles"
import { useSelector } from 'state'
import styled from 'styles'
export default function Brush() {
export default function Brush(): JSX.Element {
const brush = useSelector(({ data }) => data.brush)
if (!brush) return null
@ -16,8 +16,8 @@ export default function Brush() {
)
}
const BrushRect = styled("rect", {
fill: "$brushFill",
stroke: "$brushStroke",
const BrushRect = styled('rect', {
fill: '$brushFill',
stroke: '$brushStroke',
zStrokeWidth: 1,
})

View file

@ -1,6 +1,6 @@
import styled from 'styles'
import state, { useSelector } from 'state'
import React, { useEffect, useRef } from 'react'
import { useSelector } from 'state'
import React, { useRef } from 'react'
import useZoomEvents from 'hooks/useZoomEvents'
import useCamera from 'hooks/useCamera'
import Defs from './defs'
@ -8,12 +8,11 @@ import Page from './page'
import Brush from './brush'
import Bounds from './bounds/bounding-box'
import BoundsBg from './bounds/bounds-bg'
import Selected from './selected'
import Handles from './bounds/handles'
import useCanvasEvents from 'hooks/useCanvasEvents'
import ContextMenu from './context-menu/context-menu'
export default function Canvas() {
export default function Canvas(): JSX.Element {
const rCanvas = useRef<SVGSVGElement>(null)
const rGroup = useRef<SVGGElement>(null)

View file

@ -1,5 +1,4 @@
import * as _ContextMenu from '@radix-ui/react-context-menu'
import * as _Dropdown from '@radix-ui/react-dropdown-menu'
import styled from 'styles'
import {
IconWrapper,
@ -79,7 +78,7 @@ export default function ContextMenu({
children,
}: {
children: React.ReactNode
}) {
}): JSX.Element {
const selectedShapes = useSelector(
(s) => getSelectedShapes(s.data),
deepCompareArrays
@ -357,7 +356,6 @@ function SubMenu({
}
function AlignDistributeSubMenu({
hasTwoOrMore,
hasThreeOrMore,
}: {
hasTwoOrMore: boolean
@ -474,31 +472,6 @@ function MoveToPageMenu() {
)
}
const StyledDialogContent = styled(_Dropdown.Content, {
// position: 'fixed',
// top: '50%',
// left: '50%',
// transform: 'translate(-50%, -50%)',
// minWidth: 200,
// maxWidth: 'fit-content',
// maxHeight: '85vh',
// marginTop: '-5vh',
minWidth: 128,
backgroundColor: '$panel',
borderRadius: '4px',
overflow: 'hidden',
pointerEvents: 'all',
userSelect: 'none',
zIndex: 200,
padding: 2,
border: '1px solid $panel',
boxShadow: '0px 2px 4px rgba(0,0,0,.2)',
'&:focus': {
outline: 'none',
},
})
const StyledArrow = styled(_ContextMenu.Arrow, {
fill: 'white',
})

View file

@ -1,7 +1,7 @@
import React, { useEffect, useRef } from 'react'
import styled from 'styles'
export default function Cursor() {
export default function Cursor(): JSX.Element {
const rCursor = useRef<SVGSVGElement>(null)
useEffect(() => {

View file

@ -1,11 +1,11 @@
import { getShapeStyle } from 'lib/shape-styles'
import { getShapeUtils } from 'lib/shape-utils'
import { getShapeStyle } from 'state/shape-styles'
import { getShapeUtils } from 'state/shape-utils'
import React, { memo } from 'react'
import { useSelector } from 'state'
import { deepCompareArrays, getCurrentCamera, getPage } from 'utils/utils'
import { DotCircle, Handle } from './misc'
export default function Defs() {
export default function Defs(): JSX.Element {
const zoom = useSelector((s) => getCurrentCamera(s.data).zoom)
const currentPageShapeIds = useSelector(({ data }) => {

View file

@ -1,13 +1,8 @@
import { getShapeUtils } from 'lib/shape-utils'
import state, { useSelector } from 'state'
import { Bounds, GroupShape, PageState } from 'types'
import { getShapeUtils } from 'state/shape-utils'
import { useSelector } from 'state'
import { Bounds, PageState } from 'types'
import { boundsCollide, boundsContain } from 'utils/bounds'
import {
deepCompareArrays,
getPage,
getViewport,
screenToWorld,
} from 'utils/utils'
import { deepCompareArrays, getPage, getViewport } from 'utils/utils'
import Shape from './shape'
/*
@ -20,7 +15,7 @@ const noOffset = [0, 0]
const viewportCache = new WeakMap<PageState, Bounds>()
export default function Page() {
export default function Page(): JSX.Element {
const currentPageShapeIds = useSelector((s) => {
const page = getPage(s.data)
const pageState = s.data.pageStates[page.id]

View file

@ -6,13 +6,10 @@ import {
getSelectedIds,
setToArray,
} from 'utils/utils'
import { getShapeUtils } from 'lib/shape-utils'
import useShapeEvents from 'hooks/useShapeEvents'
import { memo, useRef } from 'react'
import { ShapeType } from 'types'
import vec from 'utils/vec'
import { getShapeUtils } from 'state/shape-utils'
import { memo } from 'react'
export default function Selected() {
export default function Selected(): JSX.Element {
const currentSelectedShapeIds = useSelector(
({ data }) => setToArray(getSelectedIds(data)),
deepCompareArrays

View file

@ -1,13 +1,12 @@
import React, { useRef, memo, useEffect } from 'react'
import state, { useSelector } from 'state'
import { useSelector } from 'state'
import styled from 'styles'
import { getShapeUtils } from 'lib/shape-utils'
import { getBoundsCenter, getPage, isMobile } from 'utils/utils'
import { ShapeStyles, ShapeType, Shape as _Shape } from 'types'
import { getShapeUtils } from 'state/shape-utils'
import { getPage, isMobile } from 'utils/utils'
import { Shape as _Shape } from 'types'
import useShapeEvents from 'hooks/useShapeEvents'
import vec from 'utils/vec'
import { getShapeStyle } from 'lib/shape-styles'
import ContextMenu from 'components/canvas/context-menu/context-menu'
import { getShapeStyle } from 'state/shape-styles'
const isMobileDevice = isMobile()
@ -17,7 +16,7 @@ interface ShapeProps {
parentPoint: number[]
}
function Shape({ id, isSelecting, parentPoint }: ShapeProps) {
function Shape({ id, isSelecting, parentPoint }: ShapeProps): JSX.Element {
const rGroup = useRef<SVGGElement>(null)
const rFocusable = useRef<HTMLTextAreaElement>(null)
@ -122,12 +121,7 @@ interface RealShapeProps {
isEditing: boolean
}
const RealShape = memo(function RealShape({
id,
shape,
style,
isParent,
}: RealShapeProps) {
const RealShape = memo(function RealShape({ id, isParent }: RealShapeProps) {
return <StyledShape as="use" data-shy={isParent} href={'#' + id} />
})
@ -217,25 +211,25 @@ const StyledGroup = styled('g', {
],
})
function Label({ children }: { children: React.ReactNode }) {
return (
<text
y={4}
x={4}
fontSize={12}
fill="black"
stroke="none"
alignmentBaseline="text-before-edge"
pointerEvents="none"
>
{children}
</text>
)
}
// function Label({ children }: { children: React.ReactNode }) {
// return (
// <text
// y={4}
// x={4}
// fontSize={12}
// fill="black"
// stroke="none"
// alignmentBaseline="text-before-edge"
// pointerEvents="none"
// >
// {children}
// </text>
// )
// }
function pp(n: number[]) {
return '[' + n.map((v) => v.toFixed(1)).join(', ') + ']'
}
// function pp(n: number[]) {
// return '[' + n.map((v) => v.toFixed(1)).join(', ') + ']'
// }
export { HoverIndicator }

View file

@ -1,6 +1,10 @@
import styled from "styles"
import styled from 'styles'
export default function CodeDocs({ isHidden }: { isHidden: boolean }) {
export default function CodeDocs({
isHidden,
}: {
isHidden: boolean
}): JSX.Element {
return (
<StyledDocs isHidden={isHidden}>
<h2>Docs</h2>
@ -8,94 +12,94 @@ export default function CodeDocs({ isHidden }: { isHidden: boolean }) {
)
}
const StyledDocs = styled("div", {
position: "absolute",
backgroundColor: "$panel",
const StyledDocs = styled('div', {
position: 'absolute',
backgroundColor: '$panel',
top: 0,
left: 0,
width: "100%",
height: "100%",
width: '100%',
height: '100%',
padding: 16,
font: "$docs",
overflowY: "scroll",
userSelect: "none",
font: '$docs',
overflowY: 'scroll',
userSelect: 'none',
paddingBottom: 64,
variants: {
isHidden: {
true: {
visibility: "hidden",
visibility: 'hidden',
},
false: {
visibility: "visible",
visibility: 'visible',
},
},
},
"& ol": {},
'& ol': {},
"& li": {
'& li': {
marginTop: 8,
marginBottom: 4,
},
"& code": {
font: "$mono",
'& code': {
font: '$mono',
},
"& hr": {
margin: "32px 0",
borderColor: "$muted",
'& hr': {
margin: '32px 0',
borderColor: '$muted',
},
"& h2": {
margin: "24px 0px",
'& h2': {
margin: '24px 0px',
},
"& h3": {
'& h3': {
fontSize: 20,
margin: "48px 0px 32px 0px",
margin: '48px 0px 32px 0px',
},
"& h3 > code": {
'& h3 > code': {
fontWeight: 600,
font: "$monoheading",
font: '$monoheading',
},
"& h4": {
margin: "32px 0px 0px 0px",
'& h4': {
margin: '32px 0px 0px 0px',
},
"& h4 > code": {
font: "$monoheading",
'& h4 > code': {
font: '$monoheading',
fontSize: 16,
userSelect: "all",
userSelect: 'all',
},
"& h4 > code > i": {
'& h4 > code > i': {
fontSize: 14,
color: "$muted",
color: '$muted',
},
"& pre": {
backgroundColor: "$bounds_bg",
'& pre': {
backgroundColor: '$bounds_bg',
padding: 16,
borderRadius: 4,
userSelect: "all",
margin: "24px 0",
userSelect: 'all',
margin: '24px 0',
},
"& p > code, blockquote > code": {
backgroundColor: "$bounds_bg",
padding: "2px 4px",
'& p > code, blockquote > code': {
backgroundColor: '$bounds_bg',
padding: '2px 4px',
borderRadius: 2,
color: "$code",
color: '$code',
},
"& blockquote": {
backgroundColor: "rgba(144, 144, 144, .05)",
'& blockquote': {
backgroundColor: 'rgba(144, 144, 144, .05)',
padding: 12,
margin: "20px 0",
margin: '20px 0',
borderRadius: 8,
},
})

View file

@ -32,7 +32,7 @@ export default function CodeEditor({
onChange,
onSave,
onKey,
}: Props) {
}: Props): JSX.Element {
const { theme } = useTheme()
const rEditor = useRef<IMonacoEditor>(null)
const rMonaco = useRef<IMonaco>(null)

View file

@ -2,12 +2,11 @@
import styled from 'styles'
import { useStateDesigner } from '@state-designer/react'
import React, { useEffect, useRef } from 'react'
import { motion } from 'framer-motion'
import state, { useSelector } from 'state'
import { CodeFile } from 'types'
import CodeDocs from './code-docs'
import CodeEditor from './code-editor'
import { generateFromCode } from 'lib/code/generate'
import { generateFromCode } from 'state/code/generate'
import * as Panel from '../panel'
import { IconButton } from '../shared'
import {
@ -30,7 +29,7 @@ const getErrorLineAndColumn = (e: any) => {
}
}
export default function CodePanel() {
export default function CodePanel(): JSX.Element {
const rContainer = useRef<HTMLDivElement>(null)
const isReadOnly = useSelector((s) => s.data.isReadOnly)
const fileId = useSelector((s) => s.data.currentCodeFileId)

View file

@ -1,14 +1,8 @@
import state, { useSelector } from "state"
import styled from "styles"
import {
ControlType,
NumberCodeControl,
SelectCodeControl,
TextCodeControl,
VectorCodeControl,
} from "types"
import state, { useSelector } from 'state'
import styled from 'styles'
import { ControlType, NumberCodeControl, VectorCodeControl } from 'types'
export default function Control({ id }: { id: string }) {
export default function Control({ id }: { id: string }): JSX.Element {
const control = useSelector((s) => s.data.codeControls[id])
if (!control) return null
@ -20,10 +14,6 @@ export default function Control({ id }: { id: string }) {
<NumberControl {...control} />
) : control.type === ControlType.Vector ? (
<VectorControl {...control} />
) : control.type === ControlType.Text ? (
<TextControl {...control} />
) : control.type === ControlType.Select ? (
<SelectControl {...control} />
) : null}
</>
)
@ -39,7 +29,7 @@ function NumberControl({ id, min, max, step, value }: NumberCodeControl) {
step={step}
value={value}
onChange={(e) =>
state.send("CHANGED_CODE_CONTROL", {
state.send('CHANGED_CODE_CONTROL', {
[id]: Number(e.currentTarget.value),
})
}
@ -51,7 +41,7 @@ function NumberControl({ id, min, max, step, value }: NumberCodeControl) {
step={step}
value={value}
onChange={(e) =>
state.send("CHANGED_CODE_CONTROL", {
state.send('CHANGED_CODE_CONTROL', {
[id]: Number(e.currentTarget.value),
})
}
@ -70,7 +60,7 @@ function VectorControl({ id, value, isNormalized }: VectorCodeControl) {
step={0.01}
value={value[0]}
onChange={(e) =>
state.send("CHANGED_CODE_CONTROL", {
state.send('CHANGED_CODE_CONTROL', {
[id]: [Number(e.currentTarget.value), value[1]],
})
}
@ -82,7 +72,7 @@ function VectorControl({ id, value, isNormalized }: VectorCodeControl) {
step={0.01}
value={value[0]}
onChange={(e) =>
state.send("CHANGED_CODE_CONTROL", {
state.send('CHANGED_CODE_CONTROL', {
[id]: [Number(e.currentTarget.value), value[1]],
})
}
@ -94,7 +84,7 @@ function VectorControl({ id, value, isNormalized }: VectorCodeControl) {
step={0.01}
value={value[1]}
onChange={(e) =>
state.send("CHANGED_CODE_CONTROL", {
state.send('CHANGED_CODE_CONTROL', {
[id]: [value[0], Number(e.currentTarget.value)],
})
}
@ -106,7 +96,7 @@ function VectorControl({ id, value, isNormalized }: VectorCodeControl) {
step={0.01}
value={value[1]}
onChange={(e) =>
state.send("CHANGED_CODE_CONTROL", {
state.send('CHANGED_CODE_CONTROL', {
[id]: [value[0], Number(e.currentTarget.value)],
})
}
@ -115,28 +105,20 @@ function VectorControl({ id, value, isNormalized }: VectorCodeControl) {
)
}
function TextControl({}: TextCodeControl) {
return <></>
}
const Inputs = styled('div', {
display: 'flex',
gap: '8px',
height: '100%',
function SelectControl({}: SelectCodeControl) {
return <></>
}
const Inputs = styled("div", {
display: "flex",
gap: "8px",
height: "100%",
"& input": {
font: "$ui",
width: "64px",
fontSize: "$1",
border: "1px solid $inputBorder",
backgroundColor: "$input",
color: "$text",
height: "100%",
padding: "0px 6px",
'& input': {
font: '$ui',
width: '64px',
fontSize: '$1',
border: '1px solid $inputBorder',
backgroundColor: '$input',
color: '$text',
height: '100%',
padding: '0px 6px',
},
"& input[type='range']": {
padding: 0,

View file

@ -1,14 +1,14 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import styled from 'styles'
import React, { useEffect, useRef } from 'react'
import React, { useRef } from 'react'
import state, { useSelector } from 'state'
import { X, Code, PlayCircle } from 'react-feather'
import { X, Code } from 'react-feather'
import { IconButton } from 'components/shared'
import * as Panel from '../panel'
import Control from './control'
import { deepCompareArrays } from 'utils/utils'
export default function ControlPanel() {
export default function ControlPanel(): JSX.Element {
const rContainer = useRef<HTMLDivElement>(null)
const codeControls = useSelector(
(state) => Object.keys(state.data.codeControls),

View file

@ -11,7 +11,7 @@ import PagePanel from './page-panel/page-panel'
// import { useSelector } from 'state'
// const CodePanel = dynamic(() => import('./code-panel/code-panel'))
export default function Editor() {
export default function Editor(): JSX.Element {
useKeyboardEvents()
useLoadOnMount()

View file

@ -1,15 +1,14 @@
import styled from 'styles'
import * as ContextMenu from '@radix-ui/react-context-menu'
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import * as Dialog from '@radix-ui/react-dialog'
import { IconWrapper, RowButton } from 'components/shared'
import { CheckIcon, ChevronDownIcon, PlusIcon } from '@radix-ui/react-icons'
import { CheckIcon, PlusIcon } from '@radix-ui/react-icons'
import * as Panel from '../panel'
import state, { useSelector } from 'state'
import { useEffect, useRef, useState } from 'react'
export default function PagePanel() {
export default function PagePanel(): JSX.Element {
const rIsOpen = useRef(false)
const [isOpen, setIsOpen] = useState(false)
@ -189,30 +188,3 @@ const StyledContextMenuItem = styled(ContextMenu.Item, {
},
},
})
const StyledOverlay = styled(Dialog.Overlay, {
backgroundColor: 'rgba(0, 0, 0, .15)',
position: 'fixed',
top: 0,
right: 0,
bottom: 0,
left: 0,
})
const StyledContent = styled(Dialog.Content, {
position: 'fixed',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
minWidth: 200,
maxWidth: 'fit-content',
maxHeight: '85vh',
padding: 20,
marginTop: '-5vh',
backgroundColor: 'white',
borderRadius: 6,
'&:focus': {
outline: 'none',
},
})

View file

@ -256,7 +256,7 @@ export const DropdownContent = styled(DropdownMenu.Content, {
},
})
export function DashSolidIcon() {
export function DashSolidIcon(): JSX.Element {
return (
<svg width="24" height="24" stroke="currentColor">
<circle
@ -271,7 +271,7 @@ export function DashSolidIcon() {
)
}
export function DashDashedIcon() {
export function DashDashedIcon(): JSX.Element {
return (
<svg width="24" height="24" stroke="currentColor">
<circle
@ -289,7 +289,7 @@ export function DashDashedIcon() {
const dottedDasharray = `${50.26548 * 0.025} ${50.26548 * 0.1}`
export function DashDottedIcon() {
export function DashDottedIcon(): JSX.Element {
return (
<svg width="24" height="24" stroke="currentColor">
<circle

View file

@ -1,11 +1,9 @@
import { useStateDesigner } from '@state-designer/react'
import state from 'state'
import styled from 'styles'
import { useRef } from 'react'
export default function StatusBar() {
export default function StatusBar(): JSX.Element {
const local = useStateDesigner(state)
const { count, time } = useRenderCount()
const active = local.active.slice(1).map((s) => {
const states = s.split('.')
@ -22,11 +20,6 @@ export default function StatusBar() {
<Section>
{active.join(' | ')} | {log}
</Section>
{/* <Section
title="Renders | Time"
>
{count} | {time.toString().padStart(3, '0')}
</Section> */}
</StatusBarContainer>
)
}
@ -62,18 +55,3 @@ const Section = styled('div', {
whiteSpace: 'nowrap',
overflow: 'hidden',
})
function useRenderCount() {
const rTime = useRef(Date.now())
const rCounter = useRef(0)
rCounter.current++
const now = Date.now()
let time = now - rTime.current
if (time > 100) {
time = 0
}
rTime.current = now
return { count: rCounter.current, time }
}

View file

@ -61,7 +61,7 @@ export default function AlignDistribute({
}: {
hasTwoOrMore: boolean
hasThreeOrMore: boolean
}) {
}): JSX.Element {
return (
<Container>
<IconButton

View file

@ -1,5 +1,5 @@
import { IconButton } from 'components/shared'
import { strokes } from 'lib/shape-styles'
import { strokes } from 'state/shape-styles'
import { ColorStyle } from 'types'
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import { Square } from 'react-feather'
@ -9,7 +9,7 @@ export default function ColorContent({
onChange,
}: {
onChange: (color: ColorStyle) => void
}) {
}): JSX.Element {
return (
<DropdownContent sideOffset={8} side="bottom">
{Object.keys(strokes).map((color: ColorStyle) => (

View file

@ -1,5 +1,5 @@
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import { strokes } from 'lib/shape-styles'
import { strokes } from 'state/shape-styles'
import { ColorStyle } from 'types'
import { RowButton, IconWrapper } from '../shared'
import { Square } from 'react-feather'
@ -10,7 +10,7 @@ interface Props {
onChange: (color: ColorStyle) => void
}
export default function ColorPicker({ color, onChange }: Props) {
export default function ColorPicker({ color, onChange }: Props): JSX.Element {
return (
<DropdownMenu.Root>
<DropdownMenu.Trigger

View file

@ -17,7 +17,7 @@ interface Props {
dash: DashStyle
}
export default function DashPicker({ dash }: Props) {
export default function DashPicker({ dash }: Props): JSX.Element {
return (
<Group name="Dash" onValueChange={handleChange}>
<Item

View file

@ -1,6 +1,6 @@
import * as Checkbox from '@radix-ui/react-checkbox'
import { CheckIcon } from '@radix-ui/react-icons'
import { strokes } from 'lib/shape-styles'
import { strokes } from 'state/shape-styles'
import { Square } from 'react-feather'
import { IconWrapper, RowButton } from '../shared'
@ -9,7 +9,10 @@ interface Props {
onChange: (isFilled: boolean | string) => void
}
export default function IsFilledPicker({ isFilled, onChange }: Props) {
export default function IsFilledPicker({
isFilled,
onChange,
}: Props): JSX.Element {
return (
<Checkbox.Root
as={RowButton}

View file

@ -1,12 +1,12 @@
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import { IconButton } from 'components/shared'
import Tooltip from 'components/tooltip'
import { strokes } from 'lib/shape-styles'
import { strokes } from 'state/shape-styles'
import { Square } from 'react-feather'
import state, { useSelector } from 'state'
import ColorContent from './color-content'
export default function QuickColorSelect() {
export default function QuickColorSelect(): JSX.Element {
const color = useSelector((s) => s.values.selectedStyle.color)
return (

View file

@ -17,7 +17,7 @@ const dashes = {
[DashStyle.Dotted]: <DashDottedIcon />,
}
export default function QuickdashSelect() {
export default function QuickdashSelect(): JSX.Element {
const dash = useSelector((s) => s.values.selectedStyle.dash)
return (

View file

@ -12,7 +12,7 @@ const sizes = {
[SizeStyle.Large]: 22,
}
export default function QuickSizeSelect() {
export default function QuickSizeSelect(): JSX.Element {
const size = useSelector((s) => s.values.selectedStyle.size)
return (

View file

@ -8,7 +8,7 @@ function handleChange(size: string) {
state.send('CHANGED_STYLE', { size })
}
export default function SizePicker({ size }: { size: SizeStyle }) {
export default function SizePicker({ size }: { size: SizeStyle }): JSX.Element {
return (
<Group name="width" onValueChange={handleChange}>
<Item

View file

@ -36,7 +36,7 @@ import QuickSizeSelect from './quick-size-select'
import QuickdashSelect from './quick-dash-select'
import Tooltip from 'components/tooltip'
export default function StylePanel() {
export default function StylePanel(): JSX.Element {
const rContainer = useRef<HTMLDivElement>(null)
const isOpen = useSelector((s) => s.data.settings.isStyleOpen)
@ -69,7 +69,7 @@ export default function StylePanel() {
// information, based on the user's current selection. We might have to keep
// track of this data manually within our state.
function SelectedShapeStyles() {
function SelectedShapeStyles(): JSX.Element {
const selectedIds = useSelector(
(s) => setToArray(getSelectedIds(s.data)),
deepCompareArrays

View file

@ -2,13 +2,10 @@ import {
ArrowTopRightIcon,
CircleIcon,
CursorArrowIcon,
DividerHorizontalIcon,
DotIcon,
LockClosedIcon,
LockOpen1Icon,
Pencil1Icon,
Pencil2Icon,
SewingPinIcon,
SquareIcon,
TextIcon,
} from '@radix-ui/react-icons'
@ -22,19 +19,14 @@ import Zoom from './zoom'
import Tooltip from '../tooltip'
const selectArrowTool = () => state.send('SELECTED_ARROW_TOOL')
const selectCircleTool = () => state.send('SELECTED_CIRCLE_TOOL')
const selectDotTool = () => state.send('SELECTED_DOT_TOOL')
const selectDrawTool = () => state.send('SELECTED_DRAW_TOOL')
const selectEllipseTool = () => state.send('SELECTED_ELLIPSE_TOOL')
const selectLineTool = () => state.send('SELECTED_LINE_TOOL')
const selectPolylineTool = () => state.send('SELECTED_POLYLINE_TOOL')
const selectTextTool = () => state.send('SELECTED_TEXT_TOOL')
const selectRayTool = () => state.send('SELECTED_RAY_TOOL')
const selectRectangleTool = () => state.send('SELECTED_RECTANGLE_TOOL')
const selectSelectTool = () => state.send('SELECTED_SELECT_TOOL')
const selectToolLock = () => state.send('TOGGLED_TOOL_LOCK')
export default function ToolsPanel() {
export default function ToolsPanel(): JSX.Element {
const activeTool = useSelector((s) => s.data.activeTool)
const isToolLocked = useSelector((s) => s.data.settings.isToolLocked)

View file

@ -1,6 +1,6 @@
import { IconButton } from 'components/shared'
import { RotateCcw, RotateCw, Trash2 } from 'react-feather'
import state, { useSelector } from 'state'
import state from 'state'
import styled from 'styles'
import Tooltip from '../tooltip'
@ -8,7 +8,7 @@ const undo = () => state.send('UNDO')
const redo = () => state.send('REDO')
const clear = () => state.send('CLEARED_PAGE')
export default function UndoRedo() {
export default function UndoRedo(): JSX.Element {
return (
<Container size={{ '@sm': 'small' }}>
<Tooltip label="Undo">

View file

@ -10,7 +10,7 @@ const zoomOut = () => state.send('ZOOMED_OUT')
const zoomToFit = () => state.send('ZOOMED_TO_FIT')
const zoomToActual = () => state.send('ZOOMED_TO_ACTUAL')
export default function Zoom() {
export default function Zoom(): JSX.Element {
return (
<Container size={{ '@sm': 'small' }}>
<Tooltip label="Zoom Out">

View file

@ -10,7 +10,7 @@ export default function Tooltip({
children: React.ReactNode
label: string
side?: 'bottom' | 'left' | 'right' | 'top'
}) {
}): JSX.Element {
return (
<_Tooltip.Root>
<_Tooltip.Trigger as="span">{children}</_Tooltip.Trigger>

2
decs.d.ts vendored
View file

@ -20,7 +20,7 @@ interface FontFace {
interface FontFaceSet {
readonly status: FontFaceSetStatus
readonly ready: Promise<FontFaceSet>
check(font: string, text?: string): Boolean
check(font: string, text?: string): boolean
load(font: string, text?: string): Promise<FontFace[]>
}

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { useCallback } from 'react'
import { fastTransform } from 'state/hacks'
import inputs from 'state/inputs'

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import React, { useEffect } from 'react'
import state from 'state'
import storage from 'state/storage'

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { MutableRefObject, useCallback } from 'react'
import state from 'state'
import {
@ -7,7 +8,6 @@ import {
fastTranslate,
} from 'state/hacks'
import inputs from 'state/inputs'
import { isMobile } from 'utils/utils'
export default function useCanvasEvents(
rCanvas: MutableRefObject<SVGGElement>
@ -58,7 +58,7 @@ export default function useCanvasEvents(
state.send('STOPPED_POINTING', { id: 'canvas', ...inputs.pointerUp(e) })
}, [])
const handleTouchStart = useCallback((e: React.TouchEvent) => {
const handleTouchStart = useCallback(() => {
// if (isMobile()) {
// if (e.touches.length === 2) {
// state.send('TOUCH_UNDO')

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import router from 'next/router'
import { useEffect } from 'react'
import * as gtag from 'utils/gtag'

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { MutableRefObject, useCallback } from 'react'
import state from 'state'
import inputs from 'state/inputs'

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { useEffect } from 'react'
import state from 'state'
import { MoveType } from 'types'
@ -157,6 +158,7 @@ export default function useKeyboardEvents() {
}
case 'o': {
if (metaKey(e)) {
break
} else {
state.send('SELECTED_DOT_TOOL', getKeyboardEventInfo(e))
}
@ -204,6 +206,7 @@ export default function useKeyboardEvents() {
}
case 'i': {
if (metaKey(e)) {
break
} else {
state.send('SELECTED_CIRCLE_TOOL', getKeyboardEventInfo(e))
}
@ -221,6 +224,7 @@ export default function useKeyboardEvents() {
}
case 'y': {
if (metaKey(e)) {
break
} else {
state.send('SELECTED_RAY_TOOL', getKeyboardEventInfo(e))
}
@ -228,6 +232,7 @@ export default function useKeyboardEvents() {
}
case 'p': {
if (metaKey(e)) {
break
} else {
state.send('SELECTED_POLYLINE_TOOL', getKeyboardEventInfo(e))
}
@ -235,6 +240,7 @@ export default function useKeyboardEvents() {
}
case 'r': {
if (metaKey(e)) {
break
} else {
state.send('SELECTED_RECTANGLE_TOOL', getKeyboardEventInfo(e))
}

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { useEffect } from 'react'
import state from 'state'

View file

@ -1,4 +1,5 @@
import React, { MutableRefObject, useCallback, useRef } from 'react'
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import React, { MutableRefObject, useCallback } from 'react'
import state from 'state'
import inputs from 'state/inputs'

View file

@ -1,12 +1,13 @@
import { useCallback } from "react"
import state, { useSelector } from "state"
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { useCallback } from 'react'
import state, { useSelector } from 'state'
export default function useTheme() {
const theme = useSelector((state) =>
state.data.settings.isDarkMode ? "dark" : "light"
state.data.settings.isDarkMode ? 'dark' : 'light'
)
const toggleTheme = useCallback(() => state.send("TOGGLED_THEME"), [])
const toggleTheme = useCallback(() => state.send('TOGGLED_THEME'), [])
return { theme, toggleTheme }
}

View file

@ -1,4 +1,5 @@
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'
@ -47,13 +48,12 @@ export default function useZoomEvents() {
rPinchPoint.current = origin
}
const [distanceDelta, angleDelta] = vec.sub(rPinchDa.current, da)
const [distanceDelta] = vec.sub(rPinchDa.current, da)
fastPinchCamera(
origin,
vec.sub(rPinchPoint.current, origin),
distanceDelta,
angleDelta
distanceDelta
)
rPinchDa.current = da

19
jest.config.js Normal file
View file

@ -0,0 +1,19 @@
module.exports = {
roots: ['<rootDir>'],
testEnvironment: 'jsdom',
moduleFileExtensions: ['ts', 'tsx', 'js', 'json', 'jsx'],
testPathIgnorePatterns: ['<rootDir>[/\\\\](node_modules|.next)[/\\\\]'],
transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.(ts|tsx)$'],
transform: {
'^.+\\.(ts|tsx)$': 'babel-jest',
},
modulePaths: ['<rootDir>'],
watchPlugins: [
'jest-watch-typeahead/filename',
'jest-watch-typeahead/testname',
],
moduleNameMapper: {
'\\.(css|less|sass|scss)$': 'identity-obj-proxy',
'\\.(gif|ttf|eot|svg|png)$': '<rootDir>/test/__mocks__/fileMock.js',
},
}

View file

@ -1,388 +0,0 @@
import {
Bounds,
Shape,
ShapeType,
Corner,
Edge,
ShapeStyles,
ShapeBinding,
Mutable,
ShapeByType,
} from 'types'
import vec from 'utils/vec'
import {
getBoundsCenter,
getBoundsFromPoints,
getRotatedCorners,
} from 'utils/utils'
import {
boundsCollidePolygon,
boundsContainPolygon,
pointInBounds,
} from 'utils/bounds'
import { uniqueId } from 'utils/utils'
import circle from './circle'
import dot from './dot'
import polyline from './polyline'
import rectangle from './rectangle'
import ellipse from './ellipse'
import line from './line'
import ray from './ray'
import draw from './draw'
import arrow from './arrow'
import group from './group'
import text from './text'
import React from 'react'
/*
Shape Utiliies
A shape utility is an object containing utility methods for each type of shape
in the application. While shapes may be very different, each shape must support
a common set of utility methods, such as hit tests or translations, that
Operations throughout the app will call these utility methods
when performing tests (such as hit tests) or mutations, such as translations.
*/
export interface ShapeUtility<K extends Shape> {
// A cache for the computed bounds of this kind of shape.
boundsCache: WeakMap<K, Bounds>
// Whether to show transform controls when this shape is selected.
canTransform: boolean
// Whether the shape's aspect ratio can change.
canChangeAspectRatio: boolean
// Whether the shape's style can be filled.
canStyleFill: boolean
// Whether the shape may be edited in an editing mode
canEdit: boolean
// Whether the shape is a foreign object.
isForeignObject: boolean
// Whether the shape can contain other shapes.
isParent: boolean
// Whether the shape is only shown when on hovered.
isShy: boolean
// Create a new shape.
create(props: Partial<K>): K
// Update a shape's styles
applyStyles(
this: ShapeUtility<K>,
shape: Mutable<K>,
style: Partial<ShapeStyles>
): ShapeUtility<K>
translateBy(
this: ShapeUtility<K>,
shape: Mutable<K>,
point: number[]
): ShapeUtility<K>
translateTo(
this: ShapeUtility<K>,
shape: Mutable<K>,
point: number[]
): ShapeUtility<K>
rotateBy(
this: ShapeUtility<K>,
shape: Mutable<K>,
rotation: number
): ShapeUtility<K>
rotateTo(
this: ShapeUtility<K>,
shape: Mutable<K>,
rotation: number,
delta: number
): ShapeUtility<K>
// Transform to fit a new bounding box when more than one shape is selected.
transform(
this: ShapeUtility<K>,
shape: Mutable<K>,
bounds: Bounds,
info: {
type: Edge | Corner
initialShape: K
scaleX: number
scaleY: number
transformOrigin: number[]
}
): ShapeUtility<K>
// Transform a single shape to fit a new bounding box.
transformSingle(
this: ShapeUtility<K>,
shape: Mutable<K>,
bounds: Bounds,
info: {
type: Edge | Corner
initialShape: K
scaleX: number
scaleY: number
transformOrigin: number[]
}
): ShapeUtility<K>
setProperty<P extends keyof K>(
this: ShapeUtility<K>,
shape: Mutable<K>,
prop: P,
value: K[P]
): ShapeUtility<K>
// Respond when any child of this shape changes.
onChildrenChange(
this: ShapeUtility<K>,
shape: Mutable<K>,
children: Shape[]
): ShapeUtility<K>
// Respond when a user moves one of the shape's bound elements.
onBindingChange(
this: ShapeUtility<K>,
shape: Mutable<K>,
bindings: Record<string, ShapeBinding>
): ShapeUtility<K>
// Respond when a user moves one of the shape's handles.
onHandleChange(
this: ShapeUtility<K>,
shape: Mutable<K>,
handle: Partial<K['handles']>
): ShapeUtility<K>
// Respond when a user double clicks the shape's bounds.
onBoundsReset(this: ShapeUtility<K>, shape: Mutable<K>): ShapeUtility<K>
// Respond when a user double clicks the center of the shape.
onDoubleFocus(this: ShapeUtility<K>, shape: Mutable<K>): ShapeUtility<K>
// Clean up changes when a session ends.
onSessionComplete(this: ShapeUtility<K>, shape: Mutable<K>): ShapeUtility<K>
// Render a shape to JSX.
render(
this: ShapeUtility<K>,
shape: K,
info: {
isEditing: boolean
ref?: React.MutableRefObject<HTMLTextAreaElement>
}
): JSX.Element
invalidate(this: ShapeUtility<K>, shape: K): ShapeUtility<K>
// Get the bounds of the a shape.
getBounds(this: ShapeUtility<K>, shape: K): Bounds
// Get the routated bounds of the a shape.
getRotatedBounds(this: ShapeUtility<K>, shape: K): Bounds
// Get the center of the shape
getCenter(this: ShapeUtility<K>, shape: K): number[]
// Test whether a point lies within a shape.
hitTest(this: ShapeUtility<K>, shape: K, test: number[]): boolean
// Test whether bounds collide with or contain a shape.
hitTestBounds(this: ShapeUtility<K>, shape: K, bounds: Bounds): boolean
shouldDelete(this: ShapeUtility<K>, shape: K): boolean
}
// A mapping of shape types to shape utilities.
const shapeUtilityMap: Record<ShapeType, ShapeUtility<Shape>> = {
[ShapeType.Circle]: circle,
[ShapeType.Dot]: dot,
[ShapeType.Polyline]: polyline,
[ShapeType.Rectangle]: rectangle,
[ShapeType.Ellipse]: ellipse,
[ShapeType.Line]: line,
[ShapeType.Ray]: ray,
[ShapeType.Draw]: draw,
[ShapeType.Arrow]: arrow,
[ShapeType.Text]: text,
[ShapeType.Group]: group,
}
/**
* A helper to retrieve a shape utility based on a shape object.
* @param shape
* @returns
*/
export function getShapeUtils<T extends Shape>(shape: T): ShapeUtility<T> {
return shapeUtilityMap[shape?.type] as ShapeUtility<T>
}
function getDefaultShapeUtil<T extends Shape>(): ShapeUtility<T> {
return {
boundsCache: new WeakMap(),
canTransform: true,
canChangeAspectRatio: true,
canStyleFill: true,
canEdit: false,
isShy: false,
isParent: false,
isForeignObject: false,
create(props) {
return {
id: uniqueId(),
isGenerated: false,
point: [0, 0],
name: 'Shape',
parentId: 'page1',
childIndex: 0,
rotation: 0,
isAspectRatioLocked: false,
isLocked: false,
isHidden: false,
...props,
} as T
},
render(shape) {
return <circle id={shape.id} />
},
translateBy(shape, delta) {
shape.point = vec.round(vec.add(shape.point, delta))
return this
},
translateTo(shape, point) {
shape.point = vec.round(point)
return this
},
rotateTo(shape, rotation) {
shape.rotation = rotation
return this
},
rotateBy(shape, rotation) {
shape.rotation += rotation
return this
},
transform(shape, bounds) {
shape.point = [bounds.minX, bounds.minY]
return this
},
transformSingle(shape, bounds, info) {
return this.transform(shape, bounds, info)
},
onChildrenChange() {
return this
},
onBindingChange() {
return this
},
onHandleChange() {
return this
},
onDoubleFocus() {
return this
},
onBoundsReset() {
return this
},
onSessionComplete() {
return this
},
getBounds(shape) {
const [x, y] = shape.point
return {
minX: x,
minY: y,
maxX: x + 1,
maxY: y + 1,
width: 1,
height: 1,
}
},
getRotatedBounds(shape) {
return getBoundsFromPoints(
getRotatedCorners(this.getBounds(shape), shape.rotation)
)
},
getCenter(shape) {
return getBoundsCenter(this.getBounds(shape))
},
hitTest(shape, point) {
return pointInBounds(point, this.getBounds(shape))
},
hitTestBounds(shape, brushBounds) {
const rotatedCorners = getRotatedCorners(
this.getBounds(shape),
shape.rotation
)
return (
boundsContainPolygon(brushBounds, rotatedCorners) ||
boundsCollidePolygon(brushBounds, rotatedCorners)
)
},
setProperty(shape, prop, value) {
shape[prop] = value
return this
},
applyStyles(shape, style) {
Object.assign(shape.style, style)
return this
},
shouldDelete(shape) {
return false
},
invalidate(shape) {
this.boundsCache.delete(shape)
return this
},
}
}
/**
* A factory of shape utilities, with typing enforced.
* @param shape
* @returns
*/
export function registerShapeUtils<K extends Shape>(
shapeUtil: Partial<ShapeUtility<K>>
): ShapeUtility<K> {
return Object.freeze({ ...getDefaultShapeUtil<K>(), ...shapeUtil })
}
export function createShape<T extends ShapeType>(
type: T,
props: Partial<ShapeByType<T>>
): ShapeByType<T> {
return shapeUtilityMap[type].create(props) as ShapeByType<T>
}
export default shapeUtilityMap

View file

@ -1,20 +0,0 @@
const withPWA = require('next-pwa')
const { withSentryConfig } = require('@sentry/nextjs')
const SentryWebpackPluginOptions = {
silent: process.env.NODE_ENV === 'development',
}
module.exports = withSentryConfig(
withPWA({
future: {
webpack5: true,
},
pwa: {
dest: 'public',
scope: '/',
disable: process.env.NODE_ENV === 'development',
},
}),
SentryWebpackPluginOptions
)

View file

@ -1,57 +1,85 @@
{
"name": "code-slate",
"version": "0.1.0",
"private": true,
"name": "with-typescript-eslint-jest",
"author": "@erikdstock",
"license": "MIT",
"version": "1.0.0",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"test": "yarn test:app",
"test:all": "yarn test:code",
"test:update": "yarn test:app --updateSnapshot --watchAll=false"
"type-check": "tsc --pretty --noEmit",
"format": "prettier --write .",
"lint": "eslint . --ext ts --ext tsx --ext js",
"test": "jest",
"test:watch": "jest --watchAll",
"test:update": "jest --updateSnapshot",
"test-all": "yarn lint && yarn type-check && yarn test"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"pre-push": "yarn run type-check"
}
},
"lint-staged": {
"*.@(ts|tsx)": [
"yarn lint",
"yarn format"
]
},
"dependencies": {
"@monaco-editor/react": "^4.1.3",
"@monaco-editor/react": "^4.2.1",
"@radix-ui/react-checkbox": "^0.0.16",
"@radix-ui/react-context-menu": "^0.0.21",
"@radix-ui/react-context-menu": "^0.0.22",
"@radix-ui/react-dialog": "^0.0.18",
"@radix-ui/react-dropdown-menu": "^0.0.20",
"@radix-ui/react-dropdown-menu": "^0.0.21",
"@radix-ui/react-hover-card": "^0.0.3",
"@radix-ui/react-icons": "^1.0.3",
"@radix-ui/react-radio-group": "^0.0.17",
"@radix-ui/react-tooltip": "^0.0.19",
"@sentry/integrations": "^6.7.1",
"@sentry/nextjs": "^6.7.1",
"@sentry/node": "^6.7.1",
"@sentry/react": "^6.7.1",
"@sentry/tracing": "^6.7.1",
"@sentry/integrations": "^6.7.2",
"@sentry/nextjs": "^6.7.2",
"@sentry/node": "^6.7.2",
"@sentry/react": "^6.7.2",
"@sentry/tracing": "^6.7.2",
"@sentry/webpack-plugin": "^1.15.1",
"@state-designer/react": "^1.7.3",
"@stitches/react": "^0.2.1",
"@state-designer/react": "^1.7.32",
"@stitches/react": "^0.2.2",
"@types/uuid": "^8.3.0",
"browser-fs-access": "^0.17.3",
"framer-motion": "^4.1.16",
"framer-motion": "^4.1.17",
"gtag": "^1.0.1",
"idb-keyval": "^5.0.6",
"ismobilejs": "^1.1.1",
"next": "10.2.0",
"monaco-editor": "^0.25.2",
"next": "latest",
"next-auth": "^3.27.0",
"next-pwa": "^5.2.21",
"perfect-freehand": "^0.4.9",
"prettier": "^2.3.0",
"react": "17.0.2",
"react-dom": "17.0.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-feather": "^2.0.9",
"react-use-gesture": "^9.1.3",
"uuid": "^8.3.2"
},
"devDependencies": {
"@types/next": "^9.0.0",
"@types/react": "^17.0.5",
"@types/react-dom": "^17.0.3",
"@types/uuid": "^8.3.0",
"@testing-library/react": "^11.2.5",
"@types/jest": "^26.0.23",
"@types/node": "^14.14.25",
"@types/react": "^17.0.1",
"@typescript-eslint/eslint-plugin": "^4.14.2",
"@typescript-eslint/parser": "^4.14.2",
"babel-jest": "^27.0.2",
"eslint": "^7.19.0",
"eslint-config-next": "^11.0.0",
"eslint-config-prettier": "^7.2.0",
"eslint-plugin-react": "^7.19.0",
"husky": "^4.2.3",
"identity-obj-proxy": "^3.0.0",
"jest": "^27.0.4",
"monaco-editor": "^0.24.0",
"typescript": "^4.2.4"
"jest-watch-typeahead": "^0.6.1",
"lint-staged": "^10.0.10",
"prettier": "^2.3.1",
"typescript": "^4.1.3"
}
}

View file

@ -1,10 +1,10 @@
import useGtag from 'hooks/useGtag'
import { AppProps } from 'next/app'
import { globalStyles } from 'styles'
import 'styles/globals.css'
import { Provider } from 'next-auth/client'
import 'styles/globals.css'
function MyApp({ Component, pageProps }: AppProps) {
function MyApp({ Component, pageProps }: AppProps): JSX.Element {
globalStyles()
useGtag()

View file

@ -1,9 +1,19 @@
import NextDocument, { Html, Head, Main, NextScript } from 'next/document'
import NextDocument, {
Html,
Head,
Main,
NextScript,
DocumentContext,
} from 'next/document'
import { dark, getCssString } from 'styles'
import { GA_TRACKING_ID } from 'utils/gtag'
class MyDocument extends NextDocument {
static async getInitialProps(ctx) {
static async getInitialProps(ctx: DocumentContext): Promise<{
styles: JSX.Element
html: string
head?: JSX.Element[]
}> {
try {
const initialProps = await NextDocument.getInitialProps(ctx)
@ -22,10 +32,11 @@ class MyDocument extends NextDocument {
} catch (e) {
console.error(e.message)
} finally {
null
}
}
render() {
render(): JSX.Element {
return (
<Html lang="en">
<Head>

View file

@ -1,8 +1,11 @@
import { NextApiRequest, NextApiResponse } from 'next'
import { NextApiHandler, NextApiRequest, NextApiResponse } from 'next'
import NextAuth from 'next-auth'
import Providers from 'next-auth/providers'
export default function (req: NextApiRequest, res: NextApiResponse) {
export default function Auth(
req: NextApiRequest,
res: NextApiResponse
): ReturnType<NextApiHandler> {
return NextAuth(req, res, {
providers: [
Providers.GitHub({
@ -15,8 +18,7 @@ export default function (req: NextApiRequest, res: NextApiResponse) {
async redirect(url, baseUrl) {
return url.startsWith(baseUrl) ? url : baseUrl
},
async signIn(user, account, profile) {
// @ts-ignore
async signIn(user, account, profile: any) {
const canLogin = await isSponsoringMe(profile?.login)
if (canLogin) {

View file

@ -1,4 +1,4 @@
export default function CreateError() {
export default function CreateError(): JSX.Element {
return (
<div>
<button

View file

@ -1,4 +1,3 @@
// import Editor from "components/editor"
import Head from 'next/head'
import dynamic from 'next/dynamic'
import { GetServerSideProps } from 'next'
@ -6,7 +5,7 @@ import { getSession } from 'next-auth/client'
const Editor = dynamic(() => import('components/editor'), { ssr: false })
export default function Home() {
export default function Home(): JSX.Element {
return (
<>
<Head>

View file

@ -4,7 +4,7 @@ import dynamic from 'next/dynamic'
const Editor = dynamic(() => import('components/editor'), { ssr: false })
export default function Home() {
export default function Home(): JSX.Element {
return (
<>
<Head>

View file

@ -1,7 +1,6 @@
import { GetServerSideProps, NextApiRequest, NextApiResponse } from 'next'
import { signOut, signout } from 'next-auth/client'
import { signOut } from 'next-auth/client'
export default function SignOut() {
export default function SignOut(): JSX.Element {
return (
<div>
<button onClick={() => signOut()}>Sign Out</button>

View file

@ -3,7 +3,7 @@ import { getSession, signin, signout, useSession } from 'next-auth/client'
import { GetServerSideProps } from 'next'
import React from 'react'
export default function Sponsorware() {
export default function Sponsorware(): JSX.Element {
const [session, loading] = useSession()
return (
@ -27,7 +27,7 @@ export default function Sponsorware() {
<li>only available for my sponsors</li>
</ul>
<p>
If you'd like to try it out,{' '}
If you&apos;d like to try it out,{' '}
<a
href="https://github.com/sponsors/steveruizok"
target="_blank"
@ -45,7 +45,7 @@ export default function Sponsorware() {
</Button>
<Detail>
Signed in as {session?.user?.name} ({session?.user?.email}), but
it looks like you're not yet a sponsor.
it looks like you&apos;re not yet a sponsor.
<br />
Something wrong? Try <a href="/">reloading the page</a> or DM me
on <a href="https://twitter.com/steveruizok">Twitter</a>.
@ -67,7 +67,7 @@ export default function Sponsorware() {
export const getServerSideProps: GetServerSideProps = async (context) => {
const session = await getSession(context)
if (!!session?.user) {
if (session?.user) {
context.res.setHeader('Location', `/`)
context.res.statusCode = 307
}

File diff suppressed because one or more lines are too long

1
public/sw.js.map Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,6 +1,6 @@
import { getShapeUtils } from 'lib/shape-utils'
import { getShapeUtils } from './shape-utils'
import { Data, Shape } from 'types'
import { getCommonBounds, getSelectedIds, getSelectedShapes } from 'utils/utils'
import { getCommonBounds, getSelectedShapes } from 'utils/utils'
import state from './state'
class Clipboard {
@ -99,10 +99,9 @@ class Clipboard {
}
static copyStringToClipboard(string: string) {
let textarea: HTMLTextAreaElement
let result: boolean | null
textarea = document.createElement('textarea')
const textarea = document.createElement('textarea')
textarea.setAttribute('position', 'fixed')
textarea.setAttribute('top', '0')
textarea.setAttribute('readonly', 'true')

View file

@ -1,12 +1,12 @@
import CodeShape from './index'
import { uniqueId } from 'utils/utils'
import { CircleShape, ShapeType } from 'types'
import { vectorToPoint } from 'utils/utils'
import { defaultStyle } from 'lib/shape-styles'
import Utils from './utils'
import { defaultStyle } from 'state/shape-styles'
export default class Circle extends CodeShape<CircleShape> {
constructor(props = {} as Partial<CircleShape>) {
props.point = vectorToPoint(props.point)
props.point = Utils.vectorToPoint(props.point)
super({
id: uniqueId(),
@ -27,15 +27,15 @@ export default class Circle extends CodeShape<CircleShape> {
})
}
export() {
export(): CircleShape {
const shape = { ...this.shape }
shape.point = vectorToPoint(shape.point)
shape.point = Utils.vectorToPoint(shape.point)
return shape
}
get radius() {
get radius(): number {
return this.shape.radius
}
}

View file

@ -25,7 +25,7 @@ export class Control<T extends CodeControl> {
}
}
destroy() {
destroy(): void {
codeControls.delete(this.control)
delete controls[this.control.label]
}

View file

@ -1,12 +1,12 @@
import CodeShape from './index'
import { uniqueId } from 'utils/utils'
import { DotShape, ShapeType } from 'types'
import { vectorToPoint } from 'utils/utils'
import { defaultStyle } from 'lib/shape-styles'
import { defaultStyle } from 'state/shape-styles'
import Utils from './utils'
export default class Dot extends CodeShape<DotShape> {
constructor(props = {} as Partial<DotShape>) {
props.point = vectorToPoint(props.point)
props.point = Utils.vectorToPoint(props.point)
super({
id: uniqueId(),
@ -30,10 +30,10 @@ export default class Dot extends CodeShape<DotShape> {
})
}
export() {
export(): DotShape {
const shape = { ...this.shape }
shape.point = vectorToPoint(shape.point)
shape.point = Utils.vectorToPoint(shape.point)
return shape
}

View file

@ -1,12 +1,12 @@
import CodeShape from './index'
import { uniqueId } from 'utils/utils'
import { EllipseShape, ShapeType } from 'types'
import { vectorToPoint } from 'utils/utils'
import { defaultStyle } from 'lib/shape-styles'
import Utils from './utils'
import { defaultStyle } from 'state/shape-styles'
export default class Ellipse extends CodeShape<EllipseShape> {
constructor(props = {} as Partial<EllipseShape>) {
props.point = vectorToPoint(props.point)
props.point = Utils.vectorToPoint(props.point)
super({
id: uniqueId(),
@ -28,19 +28,19 @@ export default class Ellipse extends CodeShape<EllipseShape> {
})
}
export() {
export(): EllipseShape {
const shape = { ...this.shape }
shape.point = vectorToPoint(shape.point)
shape.point = Utils.vectorToPoint(shape.point)
return shape
}
get radiusX() {
get radiusX(): number {
return this.shape.radiusX
}
get radiusY() {
get radiusY(): number {
return this.shape.radiusY
}
}

View file

@ -9,7 +9,7 @@ import Vector from './vector'
import Utils from './utils'
import { NumberControl, VectorControl, codeControls, controls } from './control'
import { codeShapes } from './index'
import { CodeControl, Data } from 'types'
import { CodeControl, Data, Shape } from 'types'
const baseScope = {
Dot,
@ -30,7 +30,13 @@ const baseScope = {
* collected shapes as an array.
* @param code
*/
export function generateFromCode(data: Data, code: string) {
export function generateFromCode(
data: Data,
code: string
): {
shapes: Shape[]
controls: CodeControl[]
} {
codeControls.clear()
codeShapes.clear()
;(window as any).isUpdatingCode = false
@ -55,7 +61,12 @@ export function generateFromCode(data: Data, code: string) {
* collected shapes as an array.
* @param code
*/
export function updateFromCode(data: Data, code: string) {
export function updateFromCode(
data: Data,
code: string
): {
shapes: Shape[]
} {
codeShapes.clear()
;(window as any).isUpdatingCode = true
;(window as any).currentPageId = data.currentPageId
@ -66,7 +77,7 @@ export function updateFromCode(data: Data, code: string) {
...baseScope,
currentPageId,
controls: Object.fromEntries(
Object.entries(controls).map(([id, control]) => [
Object.entries(controls).map(([_, control]) => [
control.label,
control.value,
])

View file

@ -1,12 +1,8 @@
import { Mutable, Shape } from 'types'
import shapeUtilityMap, {
createShape,
getShapeUtils,
ShapeUtility,
} from 'lib/shape-utils'
import { Mutable, Shape, ShapeUtility } from 'types'
import { createShape, getShapeUtils } from 'state/shape-utils'
import vec from 'utils/vec'
import Vector from './vector'
import { vectorToPoint } from 'utils/utils'
import Utils from './utils'
export const codeShapes = new Set<CodeShape<Shape>>([])
@ -24,48 +20,48 @@ export default class CodeShape<T extends Shape> {
codeShapes.add(this)
}
destroy() {
destroy(): void {
codeShapes.delete(this)
}
moveTo(point: Vector) {
this.utils.setProperty(this._shape, 'point', vectorToPoint(point))
moveTo(point: Vector): CodeShape<T> {
this.utils.setProperty(this._shape, 'point', Utils.vectorToPoint(point))
return this
}
translate(delta: Vector) {
translate(delta: Vector): CodeShape<T> {
this.utils.setProperty(
this._shape,
'point',
vec.add(this._shape.point, vectorToPoint(delta))
vec.add(this._shape.point, Utils.vectorToPoint(delta))
)
return this
}
rotate(rotation: number) {
rotate(rotation: number): CodeShape<T> {
this.utils.setProperty(this._shape, 'rotation', rotation)
return this
}
getBounds() {
getBounds(): CodeShape<T> {
this.utils.getBounds(this.shape)
return this
}
hitTest(point: Vector) {
this.utils.hitTest(this.shape, vectorToPoint(point))
hitTest(point: Vector): CodeShape<T> {
this.utils.hitTest(this.shape, Utils.vectorToPoint(point))
return this
}
get shape() {
get shape(): T {
return this._shape
}
get point() {
get point(): number[] {
return [...this.shape.point]
}
get rotation() {
get rotation(): number {
return this.shape.rotation
}
}

View file

@ -1,13 +1,13 @@
import CodeShape from './index'
import { uniqueId } from 'utils/utils'
import { LineShape, ShapeType } from 'types'
import { vectorToPoint } from 'utils/utils'
import { defaultStyle } from 'lib/shape-styles'
import { defaultStyle } from 'state/shape-styles'
import Utils from './utils'
export default class Line extends CodeShape<LineShape> {
constructor(props = {} as Partial<LineShape>) {
props.point = vectorToPoint(props.point)
props.direction = vectorToPoint(props.direction)
props.point = Utils.vectorToPoint(props.point)
props.direction = Utils.vectorToPoint(props.direction)
super({
id: uniqueId(),
@ -32,16 +32,16 @@ export default class Line extends CodeShape<LineShape> {
})
}
export() {
export(): LineShape {
const shape = { ...this.shape }
shape.point = vectorToPoint(shape.point)
shape.direction = vectorToPoint(shape.direction)
shape.point = Utils.vectorToPoint(shape.point)
shape.direction = Utils.vectorToPoint(shape.direction)
return shape
}
get direction() {
get direction(): number[] {
return this.shape.direction
}
}

View file

@ -1,13 +1,13 @@
import CodeShape from './index'
import { uniqueId } from 'utils/utils'
import { PolylineShape, ShapeType } from 'types'
import { vectorToPoint } from 'utils/utils'
import { defaultStyle } from 'lib/shape-styles'
import { defaultStyle } from 'state/shape-styles'
import Utils from './utils'
export default class Polyline extends CodeShape<PolylineShape> {
constructor(props = {} as Partial<PolylineShape>) {
props.point = vectorToPoint(props.point)
props.points = props.points.map(vectorToPoint)
props.point = Utils.vectorToPoint(props.point)
props.points = props.points.map(Utils.vectorToPoint)
super({
id: uniqueId(),
@ -28,16 +28,16 @@ export default class Polyline extends CodeShape<PolylineShape> {
})
}
export() {
export(): PolylineShape {
const shape = { ...this.shape }
shape.point = vectorToPoint(shape.point)
shape.points = shape.points.map(vectorToPoint)
shape.point = Utils.vectorToPoint(shape.point)
shape.points = shape.points.map(Utils.vectorToPoint)
return shape
}
get points() {
get points(): number[][] {
return this.shape.points
}
}

View file

@ -1,13 +1,13 @@
import CodeShape from './index'
import { uniqueId } from 'utils/utils'
import { RayShape, ShapeType } from 'types'
import { vectorToPoint } from 'utils/utils'
import { defaultStyle } from 'lib/shape-styles'
import { defaultStyle } from 'state/shape-styles'
import Utils from './utils'
export default class Ray extends CodeShape<RayShape> {
constructor(props = {} as Partial<RayShape>) {
props.point = vectorToPoint(props.point)
props.direction = vectorToPoint(props.direction)
props.point = Utils.vectorToPoint(props.point)
props.direction = Utils.vectorToPoint(props.direction)
super({
id: uniqueId(),
@ -32,16 +32,16 @@ export default class Ray extends CodeShape<RayShape> {
})
}
export() {
export(): RayShape {
const shape = { ...this.shape }
shape.point = vectorToPoint(shape.point)
shape.direction = vectorToPoint(shape.direction)
shape.point = Utils.vectorToPoint(shape.point)
shape.direction = Utils.vectorToPoint(shape.direction)
return shape
}
get direction() {
get direction(): number[] {
return this.shape.direction
}
}

View file

@ -1,13 +1,13 @@
import CodeShape from './index'
import { uniqueId } from 'utils/utils'
import { RectangleShape, ShapeType } from 'types'
import { vectorToPoint } from 'utils/utils'
import { defaultStyle } from 'lib/shape-styles'
import Utils from './utils'
import { defaultStyle } from 'state/shape-styles'
export default class Rectangle extends CodeShape<RectangleShape> {
constructor(props = {} as Partial<RectangleShape>) {
props.point = vectorToPoint(props.point)
props.size = vectorToPoint(props.size)
props.point = Utils.vectorToPoint(props.point)
props.size = Utils.vectorToPoint(props.size)
super({
id: uniqueId(),
@ -29,7 +29,7 @@ export default class Rectangle extends CodeShape<RectangleShape> {
})
}
get size() {
get size(): number[] {
return this.shape.size
}
}

View file

@ -2,7 +2,23 @@ import { Bounds } from 'types'
import Vector, { Point } from './vector'
export default class Utils {
static getRayRayIntersection(p0: Vector, n0: Vector, p1: Vector, n1: Vector) {
static vectorToPoint(point: number[] | Vector | undefined): number[] {
if (typeof point === 'undefined') {
return [0, 0]
}
if (point instanceof Vector) {
return [point.x, point.y]
}
return point
}
static getRayRayIntersection(
p0: Vector,
n0: Vector,
p1: Vector,
n1: Vector
): Vector {
const p0e = Vector.add(p0, n0),
p1e = Vector.add(p1, n1),
m0 = (p0e.y - p0.y) / (p0e.x - p0.x),
@ -20,7 +36,7 @@ export default class Utils {
r0: number,
P: Point | Vector,
side: number
) {
): Vector {
const v0 = Vector.cast(A)
const v1 = Vector.cast(P)
const B = Vector.lrp(v0, v1, 0.5),
@ -41,17 +57,17 @@ export default class Utils {
return side === 0 ? p.add(k) : p.sub(k)
}
static shortAngleDist(a: number, b: number) {
static shortAngleDist(a: number, b: number): number {
const max = Math.PI * 2
const da = (b - a) % max
return ((2 * da) % max) - da
}
static getSweep(C: Vector, A: Vector, B: Vector) {
static getSweep(C: Vector, A: Vector, B: Vector): number {
return Utils.shortAngleDist(Vector.ang(C, A), Vector.ang(C, B))
}
static bez1d(a: number, b: number, c: number, d: number, t: number) {
static bez1d(a: number, b: number, c: number, d: number, t: number): number {
return (
a * (1 - t) * (1 - t) * (1 - t) +
3 * b * t * (1 - t) * (1 - t) +
@ -126,7 +142,7 @@ export default class Utils {
}
}
static getExpandedBounds(a: Bounds, b: Bounds) {
static getExpandedBounds(a: Bounds, b: Bounds): Bounds {
const minX = Math.min(a.minX, b.minX),
minY = Math.min(a.minY, b.minY),
maxX = Math.max(a.maxX, b.maxX),
@ -137,7 +153,7 @@ export default class Utils {
return { minX, minY, maxX, maxY, width, height }
}
static getCommonBounds(...b: Bounds[]) {
static getCommonBounds(...b: Bounds[]): Bounds {
if (b.length < 2) return b[0]
let bounds = b[0]

View file

@ -26,43 +26,44 @@ export default class Vector {
}
}
set(v: Vector | Point) {
set(v: Vector | Point): Vector {
this.x = v.x
this.y = v.y
return this
}
copy() {
copy(): Vector {
return new Vector(this)
}
clone() {
clone(): Vector {
return this.copy()
}
toArray() {
toArray(): number[] {
return [this.x, this.y]
}
add(b: Vector) {
add(b: Vector): Vector {
this.x += b.x
this.y += b.y
return this
}
static add(a: Vector, b: Vector) {
static add(a: Vector, b: Vector): Vector {
const n = new Vector(a)
n.x += b.x
n.y += b.y
return n
}
sub(b: Vector) {
sub(b: Vector): Vector {
this.x -= b.x
this.y -= b.y
return this
}
static sub(a: Vector, b: Vector) {
static sub(a: Vector, b: Vector): Vector {
const n = new Vector(a)
n.x -= b.x
n.y -= b.y
@ -71,7 +72,7 @@ export default class Vector {
mul(b: number): Vector
mul(b: Vector): Vector
mul(b: Vector | number) {
mul(b: Vector | number): Vector {
if (b instanceof Vector) {
this.x *= b.x
this.y *= b.y
@ -82,17 +83,17 @@ export default class Vector {
return this
}
mulScalar(b: number) {
mulScalar(b: number): Vector {
return this.mul(b)
}
static mulScalar(a: Vector, b: number) {
static mulScalar(a: Vector, b: number): Vector {
return Vector.mul(a, b)
}
static mul(a: Vector, b: number): Vector
static mul(a: Vector, b: Vector): Vector
static mul(a: Vector, b: Vector | number) {
static mul(a: Vector, b: Vector | number): Vector {
const n = new Vector(a)
if (b instanceof Vector) {
n.x *= b.x
@ -106,7 +107,7 @@ export default class Vector {
div(b: number): Vector
div(b: Vector): Vector
div(b: Vector | number) {
div(b: Vector | number): Vector {
if (b instanceof Vector) {
if (b.x) {
this.x /= b.x
@ -125,7 +126,7 @@ export default class Vector {
static div(a: Vector, b: number): Vector
static div(a: Vector, b: Vector): Vector
static div(a: Vector, b: Vector | number) {
static div(a: Vector, b: Vector | number): Vector {
const n = new Vector(a)
if (b instanceof Vector) {
if (b.x) n.x /= b.x
@ -139,112 +140,112 @@ export default class Vector {
return n
}
divScalar(b: number) {
divScalar(b: number): Vector {
return this.div(b)
}
static divScalar(a: Vector, b: number) {
static divScalar(a: Vector, b: number): Vector {
return Vector.div(a, b)
}
vec(b: Vector) {
vec(b: Vector): Vector {
const { x, y } = this
this.x = b.x - x
this.y = b.y - y
return this
}
static vec(a: Vector, b: Vector) {
static vec(a: Vector, b: Vector): Vector {
const n = new Vector(a)
n.x = b.x - a.x
n.y = b.y - a.y
return n
}
pry(b: Vector) {
pry(b: Vector): number {
return this.dpr(b) / b.len()
}
static pry(a: Vector, b: Vector) {
static pry(a: Vector, b: Vector): number {
return a.dpr(b) / b.len()
}
dpr(b: Vector) {
dpr(b: Vector): number {
return this.x * b.x + this.y * b.y
}
static dpr(a: Vector, b: Vector) {
static dpr(a: Vector, b: Vector): number {
return a.x & (b.x + a.y * b.y)
}
cpr(b: Vector) {
cpr(b: Vector): number {
return this.x * b.y - b.y * this.y
}
static cpr(a: Vector, b: Vector) {
static cpr(a: Vector, b: Vector): number {
return a.x * b.y - b.y * a.y
}
tangent(b: Vector) {
tangent(b: Vector): Vector {
return this.sub(b).uni()
}
static tangent(a: Vector, b: Vector) {
static tangent(a: Vector, b: Vector): Vector {
const n = new Vector(a)
return n.sub(b).uni()
}
dist2(b: Vector) {
dist2(b: Vector): number {
return this.sub(b).len2()
}
static dist2(a: Vector, b: Vector) {
static dist2(a: Vector, b: Vector): number {
const n = new Vector(a)
return n.sub(b).len2()
}
dist(b: Vector) {
dist(b: Vector): number {
return Math.hypot(b.y - this.y, b.x - this.x)
}
static dist(a: Vector, b: Vector) {
static dist(a: Vector, b: Vector): number {
const n = new Vector(a)
return Math.hypot(b.y - n.y, b.x - n.x)
}
ang(b: Vector) {
ang(b: Vector): number {
return Math.atan2(b.y - this.y, b.x - this.x)
}
static ang(a: Vector, b: Vector) {
static ang(a: Vector, b: Vector): number {
const n = new Vector(a)
return Math.atan2(b.y - n.y, b.x - n.x)
}
med(b: Vector) {
med(b: Vector): Vector {
return this.add(b).mul(0.5)
}
static med(a: Vector, b: Vector) {
static med(a: Vector, b: Vector): Vector {
const n = new Vector(a)
return n.add(b).mul(0.5)
}
rot(r: number) {
rot(r: number): Vector {
const { x, y } = this
this.x = x * Math.cos(r) - y * Math.sin(r)
this.y = x * Math.sin(r) + y * Math.cos(r)
return this
}
static rot(a: Vector, r: number) {
static rot(a: Vector, r: number): Vector {
const n = new Vector(a)
n.x = a.x * Math.cos(r) - a.y * Math.sin(r)
n.y = a.x * Math.sin(r) + a.y * Math.cos(r)
return n
}
rotAround(b: Vector, r: number) {
rotAround(b: Vector, r: number): Vector {
const { x, y } = this
const s = Math.sin(r)
const c = Math.cos(r)
@ -258,7 +259,7 @@ export default class Vector {
return this
}
static rotAround(a: Vector, b: Vector, r: number) {
static rotAround(a: Vector, b: Vector, r: number): Vector {
const n = new Vector(a)
const s = Math.sin(r)
const c = Math.cos(r)
@ -272,173 +273,185 @@ export default class Vector {
return n
}
lrp(b: Vector, t: number) {
lrp(b: Vector, t: number): Vector {
const n = new Vector(this)
this.vec(b).mul(t).add(n)
return this
}
static lrp(a: Vector, b: Vector, t: number) {
static lrp(a: Vector, b: Vector, t: number): Vector {
const n = new Vector(a)
n.vec(b).mul(t).add(a)
return n
}
nudge(b: Vector, d: number) {
nudge(b: Vector, d: number): Vector {
this.add(b.mul(d))
return this
}
static nudge(a: Vector, b: Vector, d: number) {
static nudge(a: Vector, b: Vector, d: number): Vector {
const n = new Vector(a)
return n.add(b.mul(d))
}
nudgeToward(b: Vector, d: number) {
nudgeToward(b: Vector, d: number): Vector {
return this.nudge(Vector.vec(this, b).uni(), d)
}
static nudgeToward(a: Vector, b: Vector, d: number) {
static nudgeToward(a: Vector, b: Vector, d: number): Vector {
return Vector.nudge(a, Vector.vec(a, b).uni(), d)
}
int(b: Vector, from: number, to: number, s: number) {
int(b: Vector, from: number, to: number, s: number): Vector {
const t = (Math.max(from, to) - from) / (to - from)
this.add(Vector.mul(this, 1 - t).add(Vector.mul(b, s)))
return this
}
static int(a: Vector, b: Vector, from: number, to: number, s: number) {
static int(
a: Vector,
b: Vector,
from: number,
to: number,
s: number
): Vector {
const n = new Vector(a)
const t = (Math.max(from, to) - from) / (to - from)
n.add(Vector.mul(a, 1 - t).add(Vector.mul(b, s)))
return n
}
equals(b: Vector) {
equals(b: Vector): boolean {
return this.x === b.x && this.y === b.y
}
static equals(a: Vector, b: Vector) {
static equals(a: Vector, b: Vector): boolean {
return a.x === b.x && a.y === b.y
}
abs() {
abs(): Vector {
this.x = Math.abs(this.x)
this.y = Math.abs(this.y)
return this
}
static abs(a: Vector) {
static abs(a: Vector): Vector {
const n = new Vector(a)
n.x = Math.abs(n.x)
n.y = Math.abs(n.y)
return n
}
len() {
len(): number {
return Math.hypot(this.x, this.y)
}
static len(a: Vector) {
static len(a: Vector): number {
return Math.hypot(a.x, a.y)
}
len2() {
len2(): number {
return this.x * this.x + this.y * this.y
}
static len2(a: Vector) {
static len2(a: Vector): number {
return a.x * a.x + a.y * a.y
}
per() {
per(): Vector {
const t = this.x
this.x = this.y
this.y = -t
return this
}
static per(a: Vector) {
static per(a: Vector): Vector {
const n = new Vector(a)
n.x = n.y
n.y = -a.x
return n
}
neg() {
neg(): Vector {
this.x *= -1
this.y *= -1
return this
}
static neg(v: Vector) {
static neg(v: Vector): Vector {
const n = new Vector(v)
n.x *= -1
n.y *= -1
return n
}
uni() {
uni(): Vector {
return this.div(this.len())
}
static uni(v: Vector) {
static uni(v: Vector): Vector {
const n = new Vector(v)
return n.div(n.len())
}
normalize() {
normalize(): Vector {
return this.uni()
}
static normalize(v: Vector) {
static normalize(v: Vector): Vector {
return Vector.uni(v)
}
isLeft(center: Vector, b: Vector) {
isLeft(center: Vector, b: Vector): number {
return (
(center.x - this.x) * (b.y - this.y) - (b.x - this.x) * (center.y - b.y)
)
}
static isLeft(center: Vector, a: Vector, b: Vector) {
static isLeft(center: Vector, a: Vector, b: Vector): number {
return (center.x - a.x) * (b.y - a.y) - (b.x - a.x) * (center.y - b.y)
}
static ang3(center: Vector, a: Vector, b: Vector) {
static ang3(center: Vector, a: Vector, b: Vector): number {
const v1 = Vector.vec(center, a)
const v2 = Vector.vec(center, b)
return Vector.ang(v1, v2)
}
static clockwise(center: Vector, a: Vector, b: Vector) {
static clockwise(center: Vector, a: Vector, b: Vector): boolean {
return Vector.isLeft(center, a, b) > 0
}
static cast(v: Point | Vector) {
static cast(v: Point | Vector): Vector {
return 'cast' in v ? v : new Vector(v)
}
static from(v: Vector) {
static from(v: Vector): Vector {
return new Vector(v)
}
nearestPointOnLineThroughPoint(b: Vector, u: Vector) {
nearestPointOnLineThroughPoint(b: Vector, u: Vector): Vector {
return this.clone().add(u.clone().mul(Vector.sub(this, b).pry(u)))
}
static nearestPointOnLineThroughPoint(a: Vector, b: Vector, u: Vector) {
static nearestPointOnLineThroughPoint(
a: Vector,
b: Vector,
u: Vector
): Vector {
return a.clone().add(u.clone().mul(Vector.sub(a, b).pry(u)))
}
distanceToLineThroughPoint(b: Vector, u: Vector) {
distanceToLineThroughPoint(b: Vector, u: Vector): number {
return this.dist(Vector.nearestPointOnLineThroughPoint(b, u, this))
}
static distanceToLineThroughPoint(a: Vector, b: Vector, u: Vector) {
static distanceToLineThroughPoint(a: Vector, b: Vector, u: Vector): number {
return a.dist(Vector.nearestPointOnLineThroughPoint(b, u, a))
}
nearestPointOnLineSegment(p0: Vector, p1: Vector, clamp = true) {
nearestPointOnLineSegment(p0: Vector, p1: Vector, clamp = true): Vector {
return Vector.nearestPointOnLineSegment(this, p0, p1, clamp)
}
@ -447,7 +460,7 @@ export default class Vector {
p0: Vector,
p1: Vector,
clamp = true
) {
): Vector {
const delta = Vector.sub(p1, p0)
const length = delta.len()
const u = Vector.div(delta, length)
@ -465,7 +478,7 @@ export default class Vector {
return pt
}
distanceToLineSegment(p0: Vector, p1: Vector, clamp = true) {
distanceToLineSegment(p0: Vector, p1: Vector, clamp = true): number {
return Vector.distanceToLineSegment(this, p0, p1, clamp)
}
@ -474,7 +487,7 @@ export default class Vector {
p0: Vector,
p1: Vector,
clamp = true
) {
): number {
return Vector.dist(a, Vector.nearestPointOnLineSegment(a, p0, p1, clamp))
}
}

View file

@ -2,9 +2,9 @@ import Command from './command'
import history from '../history'
import { AlignType, Data } from 'types'
import { getCommonBounds, getPage, getSelectedShapes } from 'utils/utils'
import { getShapeUtils } from 'lib/shape-utils'
import { getShapeUtils } from 'state/shape-utils'
export default function alignCommand(data: Data, type: AlignType) {
export default function alignCommand(data: Data, type: AlignType): void {
const { currentPageId } = data
const selectedShapes = getSelectedShapes(data)
const entries = selectedShapes.map(
@ -25,7 +25,7 @@ export default function alignCommand(data: Data, type: AlignType) {
switch (type) {
case AlignType.Top: {
for (let id in boundsForShapes) {
for (const id in boundsForShapes) {
const shape = shapes[id]
getShapeUtils(shape).translateTo(shape, [
shape.point[0],
@ -35,7 +35,7 @@ export default function alignCommand(data: Data, type: AlignType) {
break
}
case AlignType.CenterVertical: {
for (let id in boundsForShapes) {
for (const id in boundsForShapes) {
const shape = shapes[id]
getShapeUtils(shape).translateTo(shape, [
shape.point[0],
@ -45,7 +45,7 @@ export default function alignCommand(data: Data, type: AlignType) {
break
}
case AlignType.Bottom: {
for (let id in boundsForShapes) {
for (const id in boundsForShapes) {
const shape = shapes[id]
getShapeUtils(shape).translateTo(shape, [
shape.point[0],
@ -55,7 +55,7 @@ export default function alignCommand(data: Data, type: AlignType) {
break
}
case AlignType.Left: {
for (let id in boundsForShapes) {
for (const id in boundsForShapes) {
const shape = shapes[id]
getShapeUtils(shape).translateTo(shape, [
commonBounds.minX,
@ -65,7 +65,7 @@ export default function alignCommand(data: Data, type: AlignType) {
break
}
case AlignType.CenterHorizontal: {
for (let id in boundsForShapes) {
for (const id in boundsForShapes) {
const shape = shapes[id]
getShapeUtils(shape).translateTo(shape, [
midX - boundsForShapes[id].width / 2,
@ -75,7 +75,7 @@ export default function alignCommand(data: Data, type: AlignType) {
break
}
case AlignType.Right: {
for (let id in boundsForShapes) {
for (const id in boundsForShapes) {
const shape = shapes[id]
getShapeUtils(shape).translateTo(shape, [
commonBounds.maxX - boundsForShapes[id].width,
@ -88,7 +88,7 @@ export default function alignCommand(data: Data, type: AlignType) {
},
undo(data) {
const { shapes } = getPage(data, currentPageId)
for (let id in boundsForShapes) {
for (const id in boundsForShapes) {
const shape = shapes[id]
const initialBounds = boundsForShapes[id]
getShapeUtils(shape).translateTo(shape, [

View file

@ -8,7 +8,7 @@ export default function arrowCommand(
data: Data,
before: ArrowSnapshot,
after: ArrowSnapshot
) {
): void {
history.execute(
data,
new Command({

View file

@ -3,7 +3,7 @@ import history from '../history'
import { Data } from 'types'
import storage from 'state/storage'
export default function changePage(data: Data, toPageId: string) {
export default function changePage(data: Data, toPageId: string): void {
const { currentPageId: fromPageId } = data
history.execute(

View file

@ -40,7 +40,7 @@ export class BaseCommand<T extends any> {
}
}
undo = (data: T) => {
undo = (data: T): void => {
if (this.manualSelection) {
this.undoFn(data)
return
@ -52,7 +52,7 @@ export class BaseCommand<T extends any> {
this.restoreBeforeSelectionState(data)
}
redo = (data: T, initial = false) => {
redo = (data: T, initial = false): void => {
if (this.manualSelection) {
this.doFn(data, initial)
@ -82,7 +82,7 @@ export class BaseCommand<T extends any> {
* the app.
*/
export default class Command extends BaseCommand<Data> {
saveSelectionState = (data: Data) => {
saveSelectionState = (data: Data): ((next: Data) => void) => {
const { currentPageId } = data
const selectedIds = setToArray(getSelectedIds(data))
return (next: Data) => {

View file

@ -5,7 +5,7 @@ import { uniqueId } from 'utils/utils'
import { current } from 'immer'
import storage from 'state/storage'
export default function createPage(data: Data, goToPage = true) {
export default function createPage(data: Data, goToPage = true): void {
const snapshot = getSnapshot(data)
history.execute(

View file

@ -2,10 +2,9 @@ import Command from './command'
import history from '../history'
import { Data } from 'types'
import { current } from 'immer'
import vec from 'utils/vec'
import storage from 'state/storage'
export default function deletePage(data: Data, pageId: string) {
export default function deletePage(data: Data, pageId: string): void {
const snapshot = getSnapshot(data, pageId)
history.execute(

View file

@ -1,21 +1,16 @@
import Command from './command'
import history from '../history'
import { TranslateSnapshot } from 'state/sessions/translate-session'
import { Data, ShapeType } from 'types'
import { Data } from 'types'
import {
getDocumentBranch,
getPage,
getPageState,
getSelectedIds,
getSelectedShapes,
setSelectedIds,
setToArray,
updateParents,
} from 'utils/utils'
import { current } from 'immer'
import { getShapeUtils } from 'lib/shape-utils'
import { getShapeUtils } from 'state/shape-utils'
export default function deleteSelected(data: Data) {
export default function deleteSelected(data: Data): void {
const { currentPageId } = data
const selectedShapes = getSelectedShapes(data)
@ -43,7 +38,7 @@ export default function deleteSelected(data: Data) {
do(data) {
const page = getPage(data, currentPageId)
for (let id of selectedIdsArr) {
for (const id of selectedIdsArr) {
const shape = page.shapes[id]
if (!shape) {
console.error('no shape ' + id)
@ -65,7 +60,7 @@ export default function deleteSelected(data: Data) {
}
}
for (let shape of childrenToDelete) {
for (const shape of childrenToDelete) {
delete page.shapes[shape.id]
}
@ -74,11 +69,11 @@ export default function deleteSelected(data: Data) {
undo(data) {
const page = getPage(data, currentPageId)
for (let shape of childrenToDelete) {
for (const shape of childrenToDelete) {
page.shapes[shape.id] = shape
}
for (let shape of childrenToDelete) {
for (const shape of childrenToDelete) {
if (shape.parentId !== data.currentPageId) {
const parent = page.shapes[shape.parentId]
getShapeUtils(parent)

View file

@ -1,23 +1,23 @@
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/utils"
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/utils'
export default function directCommand(
data: Data,
before: DirectionSnapshot,
after: DirectionSnapshot
) {
): void {
history.execute(
data,
new Command({
name: "set_direction",
category: "canvas",
name: 'set_direction',
category: 'canvas',
do(data) {
const { shapes } = getPage(data)
for (let { id, direction } of after.shapes) {
for (const { id, direction } of after.shapes) {
const shape = shapes[id] as RayShape | LineShape
shape.direction = direction
@ -26,7 +26,7 @@ export default function directCommand(
undo(data) {
const { shapes } = getPage(data, before.currentPageId)
for (let { id, direction } of after.shapes) {
for (const { id, direction } of after.shapes) {
const shape = shapes[id] as RayShape | LineShape
shape.direction = direction

View file

@ -7,9 +7,12 @@ import {
getPage,
getSelectedShapes,
} from 'utils/utils'
import { getShapeUtils } from 'lib/shape-utils'
import { getShapeUtils } from 'state/shape-utils'
export default function distributeCommand(data: Data, type: DistributeType) {
export default function distributeCommand(
data: Data,
type: DistributeType
): void {
const { currentPageId } = data
const selectedShapes = getSelectedShapes(data).filter(
@ -130,7 +133,7 @@ export default function distributeCommand(data: Data, type: DistributeType) {
},
undo(data) {
const { shapes } = getPage(data, currentPageId)
for (let id in boundsForShapes) {
for (const id in boundsForShapes) {
const shape = shapes[id]
const initialBounds = boundsForShapes[id]
getShapeUtils(shape).translateTo(shape, [

View file

@ -4,7 +4,7 @@ import { Data, DrawShape } from 'types'
import { getPage, setSelectedIds } from 'utils/utils'
import { current } from 'immer'
export default function drawCommand(data: Data, id: string) {
export default function drawCommand(data: Data, id: string): void {
const restoreShape = getPage(current(data)).shapes[id] as DrawShape
history.execute(

View file

@ -4,7 +4,6 @@ import { Data } from 'types'
import {
getCurrentCamera,
getPage,
getSelectedIds,
getSelectedShapes,
setSelectedIds,
} from 'utils/utils'
@ -12,7 +11,7 @@ import { uniqueId } from 'utils/utils'
import { current } from 'immer'
import vec from 'utils/vec'
export default function duplicateCommand(data: Data) {
export default function duplicateCommand(data: Data): void {
const { currentPageId } = data
const selectedShapes = getSelectedShapes(current(data))
const duplicates = selectedShapes.map((shape) => ({

View file

@ -3,13 +3,13 @@ import history from '../history'
import { Data } from 'types'
import { getPage } from 'utils/utils'
import { EditSnapshot } from 'state/sessions/edit-session'
import { getShapeUtils } from 'lib/shape-utils'
import { getShapeUtils } from 'state/shape-utils'
export default function editCommand(
data: Data,
before: EditSnapshot,
after: EditSnapshot
) {
): void {
history.execute(
data,
new Command({

View file

@ -1,14 +1,14 @@
import Command from './command'
import history from '../history'
import { CodeControl, Data, Shape } from 'types'
import { Data, Shape } from 'types'
import { current } from 'immer'
import { getPage, getSelectedIds, setSelectedIds } from 'utils/utils'
import { getPage, setSelectedIds } from 'utils/utils'
export default function generateCommand(
data: Data,
currentPageId: string,
generatedShapes: Shape[]
) {
): void {
const cData = current(data)
const page = getPage(cData)
@ -19,14 +19,14 @@ export default function generateCommand(
)
// Remove previous generated shapes
for (let id in currentShapes) {
for (const id in currentShapes) {
if (currentShapes[id].isGenerated) {
delete currentShapes[id]
}
}
// Add new ones
for (let shape of generatedShapes) {
for (const shape of generatedShapes) {
currentShapes[shape.id] = shape
}
@ -41,14 +41,14 @@ export default function generateCommand(
setSelectedIds(data, [])
// Remove previous generated shapes
for (let id in shapes) {
for (const id in shapes) {
if (shapes[id].isGenerated) {
delete shapes[id]
}
}
// Add new generated shapes
for (let shape of generatedShapes) {
for (const shape of generatedShapes) {
shapes[shape.id] = shape
}
},
@ -56,14 +56,14 @@ export default function generateCommand(
const { shapes } = getPage(data)
// Remove generated shapes
for (let id in shapes) {
for (const id in shapes) {
if (shapes[id].isGenerated) {
delete shapes[id]
}
}
// Restore previous generated shapes
for (let shape of prevGeneratedShapes) {
for (const shape of prevGeneratedShapes) {
shapes[shape.id] = shape
}
},

View file

@ -1,6 +1,6 @@
import Command from './command'
import history from '../history'
import { Data, GroupShape, Shape, ShapeType } from 'types'
import { Data, GroupShape, ShapeType } from 'types'
import {
getCommonBounds,
getPage,
@ -10,10 +10,10 @@ import {
setSelectedIds,
} from 'utils/utils'
import { current } from 'immer'
import { createShape, getShapeUtils } from 'lib/shape-utils'
import { createShape, getShapeUtils } from 'state/shape-utils'
import commands from '.'
export default function groupCommand(data: Data) {
export default function groupCommand(data: Data): void {
const cData = current(data)
const { currentPageId } = cData
@ -28,15 +28,9 @@ export default function groupCommand(data: Data) {
)
let newGroupParentId: string
let newGroupShape: GroupShape
let newGroupChildIndex: number
const initialShapeIds = initialShapes.map((s) => s.id)
const parentIds = Array.from(
new Set(initialShapes.map((s) => s.parentId)).values()
)
const commonBounds = getCommonBounds(
...initialShapes.map((shape) =>
getShapeUtils(shape).getRotatedBounds(shape)
@ -64,7 +58,7 @@ export default function groupCommand(data: Data) {
// Find the least-deep parent among the shapes and add the group as a child
let minDepth = Infinity
for (let parentId of initialShapes.map((shape) => shape.parentId)) {
for (const parentId of initialShapes.map((shape) => shape.parentId)) {
const depth = getShapeDepth(data, parentId)
if (depth < minDepth) {
minDepth = depth
@ -73,7 +67,7 @@ export default function groupCommand(data: Data) {
}
}
newGroupShape = createShape(ShapeType.Group, {
const newGroupShape = createShape(ShapeType.Group, {
parentId: newGroupParentId,
point: [commonBounds.minX, commonBounds.minY],
size: [commonBounds.width, commonBounds.height],

View file

@ -3,22 +3,19 @@ import history from '../history'
import { Data } from 'types'
import { getPage } from 'utils/utils'
import { HandleSnapshot } from 'state/sessions/handle-session'
import { getShapeUtils } from 'lib/shape-utils'
import vec from 'utils/vec'
import { getShapeUtils } from 'state/shape-utils'
export default function handleCommand(
data: Data,
before: HandleSnapshot,
after: HandleSnapshot
) {
): void {
history.execute(
data,
new Command({
name: 'moved_handle',
category: 'canvas',
do(data, isInitial) {
// if (isInitial) return
do(data) {
const { initialShape, currentPageId } = after
const page = getPage(data, currentPageId)
@ -27,18 +24,6 @@ export default function handleCommand(
getShapeUtils(shape)
.onHandleChange(shape, initialShape.handles)
.onSessionComplete(shape)
// const bounds = getShapeUtils(shape).getBounds(shape)
// const offset = vec.sub([bounds.minX, bounds.minY], shape.point)
// getShapeUtils(shape).translateTo(shape, vec.add(shape.point, offset))
// const { start, end, bend } = page.shapes[initialShape.id].handles
// start.point = vec.sub(start.point, offset)
// end.point = vec.sub(end.point, offset)
// bend.point = vec.sub(bend.point, offset)
},
undo(data) {
const { initialShape, currentPageId } = before

View file

@ -6,16 +6,13 @@ import {
getPage,
getPageState,
getSelectedIds,
getSelectedShapes,
getTopParentId,
setToArray,
uniqueArray,
} from 'utils/utils'
import { getShapeUtils } from 'lib/shape-utils'
import vec from 'utils/vec'
import { getShapeUtils } from 'state/shape-utils'
import storage from 'state/storage'
export default function moveToPageCommand(data: Data, newPageId: string) {
export default function moveToPageCommand(data: Data, newPageId: string): void {
const { currentPageId: oldPageId } = data
const oldPage = getPage(data)
const selectedIds = setToArray(getSelectedIds(data))

Some files were not shown because too many files have changed in this diff Show more