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 A tiny little drawing app by [steveruizok](https://twitter.com/steveruizok).
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.

View file

@ -15,7 +15,7 @@ import CornerHandle from './corner-handle'
import EdgeHandle from './edge-handle' import EdgeHandle from './edge-handle'
import RotateHandle from './rotate-handle' import RotateHandle from './rotate-handle'
export default function Bounds() { export default function Bounds(): JSX.Element {
const isBrushing = useSelector((s) => s.isIn('brushSelecting')) const isBrushing = useSelector((s) => s.isIn('brushSelecting'))
const isSelecting = useSelector((s) => s.isIn('selecting')) 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)) state.send('STOPPED_POINTING', inputs.pointerUp(e))
} }
export default function BoundsBg() { export default function BoundsBg(): JSX.Element {
const rBounds = useRef<SVGRectElement>(null) const rBounds = useRef<SVGRectElement>(null)
const bounds = useSelector((state) => state.values.selectedBounds) const bounds = useSelector((state) => state.values.selectedBounds)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,5 +1,4 @@
import * as _ContextMenu from '@radix-ui/react-context-menu' import * as _ContextMenu from '@radix-ui/react-context-menu'
import * as _Dropdown from '@radix-ui/react-dropdown-menu'
import styled from 'styles' import styled from 'styles'
import { import {
IconWrapper, IconWrapper,
@ -79,7 +78,7 @@ export default function ContextMenu({
children, children,
}: { }: {
children: React.ReactNode children: React.ReactNode
}) { }): JSX.Element {
const selectedShapes = useSelector( const selectedShapes = useSelector(
(s) => getSelectedShapes(s.data), (s) => getSelectedShapes(s.data),
deepCompareArrays deepCompareArrays
@ -357,7 +356,6 @@ function SubMenu({
} }
function AlignDistributeSubMenu({ function AlignDistributeSubMenu({
hasTwoOrMore,
hasThreeOrMore, hasThreeOrMore,
}: { }: {
hasTwoOrMore: boolean 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, { const StyledArrow = styled(_ContextMenu.Arrow, {
fill: 'white', fill: 'white',
}) })

View file

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

View file

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

View file

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

View file

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

View file

@ -1,13 +1,12 @@
import React, { useRef, memo, useEffect } from 'react' import React, { useRef, memo, useEffect } from 'react'
import state, { useSelector } from 'state' import { useSelector } from 'state'
import styled from 'styles' import styled from 'styles'
import { getShapeUtils } from 'lib/shape-utils' import { getShapeUtils } from 'state/shape-utils'
import { getBoundsCenter, getPage, isMobile } from 'utils/utils' import { getPage, isMobile } from 'utils/utils'
import { ShapeStyles, ShapeType, Shape as _Shape } from 'types' import { Shape as _Shape } from 'types'
import useShapeEvents from 'hooks/useShapeEvents' import useShapeEvents from 'hooks/useShapeEvents'
import vec from 'utils/vec' import vec from 'utils/vec'
import { getShapeStyle } from 'lib/shape-styles' import { getShapeStyle } from 'state/shape-styles'
import ContextMenu from 'components/canvas/context-menu/context-menu'
const isMobileDevice = isMobile() const isMobileDevice = isMobile()
@ -17,7 +16,7 @@ interface ShapeProps {
parentPoint: number[] parentPoint: number[]
} }
function Shape({ id, isSelecting, parentPoint }: ShapeProps) { function Shape({ id, isSelecting, parentPoint }: ShapeProps): JSX.Element {
const rGroup = useRef<SVGGElement>(null) const rGroup = useRef<SVGGElement>(null)
const rFocusable = useRef<HTMLTextAreaElement>(null) const rFocusable = useRef<HTMLTextAreaElement>(null)
@ -122,12 +121,7 @@ interface RealShapeProps {
isEditing: boolean isEditing: boolean
} }
const RealShape = memo(function RealShape({ const RealShape = memo(function RealShape({ id, isParent }: RealShapeProps) {
id,
shape,
style,
isParent,
}: RealShapeProps) {
return <StyledShape as="use" data-shy={isParent} href={'#' + id} /> return <StyledShape as="use" data-shy={isParent} href={'#' + id} />
}) })
@ -217,25 +211,25 @@ const StyledGroup = styled('g', {
], ],
}) })
function Label({ children }: { children: React.ReactNode }) { // function Label({ children }: { children: React.ReactNode }) {
return ( // return (
<text // <text
y={4} // y={4}
x={4} // x={4}
fontSize={12} // fontSize={12}
fill="black" // fill="black"
stroke="none" // stroke="none"
alignmentBaseline="text-before-edge" // alignmentBaseline="text-before-edge"
pointerEvents="none" // pointerEvents="none"
> // >
{children} // {children}
</text> // </text>
) // )
} // }
function pp(n: number[]) { // function pp(n: number[]) {
return '[' + n.map((v) => v.toFixed(1)).join(', ') + ']' // return '[' + n.map((v) => v.toFixed(1)).join(', ') + ']'
} // }
export { HoverIndicator } 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 ( return (
<StyledDocs isHidden={isHidden}> <StyledDocs isHidden={isHidden}>
<h2>Docs</h2> <h2>Docs</h2>
@ -8,94 +12,94 @@ export default function CodeDocs({ isHidden }: { isHidden: boolean }) {
) )
} }
const StyledDocs = styled("div", { const StyledDocs = styled('div', {
position: "absolute", position: 'absolute',
backgroundColor: "$panel", backgroundColor: '$panel',
top: 0, top: 0,
left: 0, left: 0,
width: "100%", width: '100%',
height: "100%", height: '100%',
padding: 16, padding: 16,
font: "$docs", font: '$docs',
overflowY: "scroll", overflowY: 'scroll',
userSelect: "none", userSelect: 'none',
paddingBottom: 64, paddingBottom: 64,
variants: { variants: {
isHidden: { isHidden: {
true: { true: {
visibility: "hidden", visibility: 'hidden',
}, },
false: { false: {
visibility: "visible", visibility: 'visible',
}, },
}, },
}, },
"& ol": {}, '& ol': {},
"& li": { '& li': {
marginTop: 8, marginTop: 8,
marginBottom: 4, marginBottom: 4,
}, },
"& code": { '& code': {
font: "$mono", font: '$mono',
}, },
"& hr": { '& hr': {
margin: "32px 0", margin: '32px 0',
borderColor: "$muted", borderColor: '$muted',
}, },
"& h2": { '& h2': {
margin: "24px 0px", margin: '24px 0px',
}, },
"& h3": { '& h3': {
fontSize: 20, fontSize: 20,
margin: "48px 0px 32px 0px", margin: '48px 0px 32px 0px',
}, },
"& h3 > code": { '& h3 > code': {
fontWeight: 600, fontWeight: 600,
font: "$monoheading", font: '$monoheading',
}, },
"& h4": { '& h4': {
margin: "32px 0px 0px 0px", margin: '32px 0px 0px 0px',
}, },
"& h4 > code": { '& h4 > code': {
font: "$monoheading", font: '$monoheading',
fontSize: 16, fontSize: 16,
userSelect: "all", userSelect: 'all',
}, },
"& h4 > code > i": { '& h4 > code > i': {
fontSize: 14, fontSize: 14,
color: "$muted", color: '$muted',
}, },
"& pre": { '& pre': {
backgroundColor: "$bounds_bg", backgroundColor: '$bounds_bg',
padding: 16, padding: 16,
borderRadius: 4, borderRadius: 4,
userSelect: "all", userSelect: 'all',
margin: "24px 0", margin: '24px 0',
}, },
"& p > code, blockquote > code": { '& p > code, blockquote > code': {
backgroundColor: "$bounds_bg", backgroundColor: '$bounds_bg',
padding: "2px 4px", padding: '2px 4px',
borderRadius: 2, borderRadius: 2,
color: "$code", color: '$code',
}, },
"& blockquote": { '& blockquote': {
backgroundColor: "rgba(144, 144, 144, .05)", backgroundColor: 'rgba(144, 144, 144, .05)',
padding: 12, padding: 12,
margin: "20px 0", margin: '20px 0',
borderRadius: 8, borderRadius: 8,
}, },
}) })

View file

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

View file

@ -2,12 +2,11 @@
import styled from 'styles' import styled from 'styles'
import { useStateDesigner } from '@state-designer/react' import { useStateDesigner } from '@state-designer/react'
import React, { useEffect, useRef } from 'react' import React, { useEffect, useRef } from 'react'
import { motion } from 'framer-motion'
import state, { useSelector } from 'state' import state, { useSelector } from 'state'
import { CodeFile } from 'types' import { CodeFile } from 'types'
import CodeDocs from './code-docs' import CodeDocs from './code-docs'
import CodeEditor from './code-editor' import CodeEditor from './code-editor'
import { generateFromCode } from 'lib/code/generate' import { generateFromCode } from 'state/code/generate'
import * as Panel from '../panel' import * as Panel from '../panel'
import { IconButton } from '../shared' import { IconButton } from '../shared'
import { 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 rContainer = useRef<HTMLDivElement>(null)
const isReadOnly = useSelector((s) => s.data.isReadOnly) const isReadOnly = useSelector((s) => s.data.isReadOnly)
const fileId = useSelector((s) => s.data.currentCodeFileId) const fileId = useSelector((s) => s.data.currentCodeFileId)

View file

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

View file

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

View file

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

View file

@ -1,15 +1,14 @@
import styled from 'styles' import styled from 'styles'
import * as ContextMenu from '@radix-ui/react-context-menu' import * as ContextMenu from '@radix-ui/react-context-menu'
import * as DropdownMenu from '@radix-ui/react-dropdown-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 { 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 * as Panel from '../panel'
import state, { useSelector } from 'state' import state, { useSelector } from 'state'
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
export default function PagePanel() { export default function PagePanel(): JSX.Element {
const rIsOpen = useRef(false) const rIsOpen = useRef(false)
const [isOpen, setIsOpen] = useState(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 ( return (
<svg width="24" height="24" stroke="currentColor"> <svg width="24" height="24" stroke="currentColor">
<circle <circle
@ -271,7 +271,7 @@ export function DashSolidIcon() {
) )
} }
export function DashDashedIcon() { export function DashDashedIcon(): JSX.Element {
return ( return (
<svg width="24" height="24" stroke="currentColor"> <svg width="24" height="24" stroke="currentColor">
<circle <circle
@ -289,7 +289,7 @@ export function DashDashedIcon() {
const dottedDasharray = `${50.26548 * 0.025} ${50.26548 * 0.1}` const dottedDasharray = `${50.26548 * 0.025} ${50.26548 * 0.1}`
export function DashDottedIcon() { export function DashDottedIcon(): JSX.Element {
return ( return (
<svg width="24" height="24" stroke="currentColor"> <svg width="24" height="24" stroke="currentColor">
<circle <circle

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,13 +2,10 @@ import {
ArrowTopRightIcon, ArrowTopRightIcon,
CircleIcon, CircleIcon,
CursorArrowIcon, CursorArrowIcon,
DividerHorizontalIcon,
DotIcon,
LockClosedIcon, LockClosedIcon,
LockOpen1Icon, LockOpen1Icon,
Pencil1Icon, Pencil1Icon,
Pencil2Icon, Pencil2Icon,
SewingPinIcon,
SquareIcon, SquareIcon,
TextIcon, TextIcon,
} from '@radix-ui/react-icons' } from '@radix-ui/react-icons'
@ -22,19 +19,14 @@ import Zoom from './zoom'
import Tooltip from '../tooltip' import Tooltip from '../tooltip'
const selectArrowTool = () => state.send('SELECTED_ARROW_TOOL') 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 selectDrawTool = () => state.send('SELECTED_DRAW_TOOL')
const selectEllipseTool = () => state.send('SELECTED_ELLIPSE_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 selectTextTool = () => state.send('SELECTED_TEXT_TOOL')
const selectRayTool = () => state.send('SELECTED_RAY_TOOL')
const selectRectangleTool = () => state.send('SELECTED_RECTANGLE_TOOL') const selectRectangleTool = () => state.send('SELECTED_RECTANGLE_TOOL')
const selectSelectTool = () => state.send('SELECTED_SELECT_TOOL') const selectSelectTool = () => state.send('SELECTED_SELECT_TOOL')
const selectToolLock = () => state.send('TOGGLED_TOOL_LOCK') 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 activeTool = useSelector((s) => s.data.activeTool)
const isToolLocked = useSelector((s) => s.data.settings.isToolLocked) const isToolLocked = useSelector((s) => s.data.settings.isToolLocked)

View file

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

View file

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

View file

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

2
decs.d.ts vendored
View file

@ -20,7 +20,7 @@ interface FontFace {
interface FontFaceSet { interface FontFaceSet {
readonly status: FontFaceSetStatus readonly status: FontFaceSetStatus
readonly ready: Promise<FontFaceSet> readonly ready: Promise<FontFaceSet>
check(font: string, text?: string): Boolean check(font: string, text?: string): boolean
load(font: string, text?: string): Promise<FontFace[]> 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 { useCallback } from 'react'
import { fastTransform } from 'state/hacks' import { fastTransform } from 'state/hacks'
import inputs from 'state/inputs' 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 React, { useEffect } from 'react'
import state from 'state' import state from 'state'
import storage from 'state/storage' 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 { MutableRefObject, useCallback } from 'react'
import state from 'state' import state from 'state'
import { import {
@ -7,7 +8,6 @@ import {
fastTranslate, fastTranslate,
} from 'state/hacks' } from 'state/hacks'
import inputs from 'state/inputs' import inputs from 'state/inputs'
import { isMobile } from 'utils/utils'
export default function useCanvasEvents( export default function useCanvasEvents(
rCanvas: MutableRefObject<SVGGElement> rCanvas: MutableRefObject<SVGGElement>
@ -58,7 +58,7 @@ export default function useCanvasEvents(
state.send('STOPPED_POINTING', { id: 'canvas', ...inputs.pointerUp(e) }) state.send('STOPPED_POINTING', { id: 'canvas', ...inputs.pointerUp(e) })
}, []) }, [])
const handleTouchStart = useCallback((e: React.TouchEvent) => { const handleTouchStart = useCallback(() => {
// if (isMobile()) { // if (isMobile()) {
// if (e.touches.length === 2) { // if (e.touches.length === 2) {
// state.send('TOUCH_UNDO') // state.send('TOUCH_UNDO')

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import router from 'next/router' import router from 'next/router'
import { useEffect } from 'react' import { useEffect } from 'react'
import * as gtag from 'utils/gtag' 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 { MutableRefObject, useCallback } from 'react'
import state from 'state' import state from 'state'
import inputs from 'state/inputs' import inputs from 'state/inputs'

View file

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

View file

@ -1,12 +1,13 @@
import { useCallback } from "react" /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import state, { useSelector } from "state" import { useCallback } from 'react'
import state, { useSelector } from 'state'
export default function useTheme() { export default function useTheme() {
const theme = useSelector((state) => 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 } 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 state from 'state'
import inputs from 'state/inputs' import inputs from 'state/inputs'
import vec from 'utils/vec' import vec from 'utils/vec'
@ -47,13 +48,12 @@ export default function useZoomEvents() {
rPinchPoint.current = origin rPinchPoint.current = origin
} }
const [distanceDelta, angleDelta] = vec.sub(rPinchDa.current, da) const [distanceDelta] = vec.sub(rPinchDa.current, da)
fastPinchCamera( fastPinchCamera(
origin, origin,
vec.sub(rPinchPoint.current, origin), vec.sub(rPinchPoint.current, origin),
distanceDelta, distanceDelta
angleDelta
) )
rPinchDa.current = da 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", "name": "with-typescript-eslint-jest",
"version": "0.1.0", "author": "@erikdstock",
"private": true, "license": "MIT",
"version": "1.0.0",
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"test": "yarn test:app", "type-check": "tsc --pretty --noEmit",
"test:all": "yarn test:code", "format": "prettier --write .",
"test:update": "yarn test:app --updateSnapshot --watchAll=false" "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": { "dependencies": {
"@monaco-editor/react": "^4.1.3", "@monaco-editor/react": "^4.2.1",
"@radix-ui/react-checkbox": "^0.0.16", "@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-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-hover-card": "^0.0.3",
"@radix-ui/react-icons": "^1.0.3", "@radix-ui/react-icons": "^1.0.3",
"@radix-ui/react-radio-group": "^0.0.17", "@radix-ui/react-radio-group": "^0.0.17",
"@radix-ui/react-tooltip": "^0.0.19", "@radix-ui/react-tooltip": "^0.0.19",
"@sentry/integrations": "^6.7.1", "@sentry/integrations": "^6.7.2",
"@sentry/nextjs": "^6.7.1", "@sentry/nextjs": "^6.7.2",
"@sentry/node": "^6.7.1", "@sentry/node": "^6.7.2",
"@sentry/react": "^6.7.1", "@sentry/react": "^6.7.2",
"@sentry/tracing": "^6.7.1", "@sentry/tracing": "^6.7.2",
"@sentry/webpack-plugin": "^1.15.1", "@sentry/webpack-plugin": "^1.15.1",
"@state-designer/react": "^1.7.3", "@state-designer/react": "^1.7.32",
"@stitches/react": "^0.2.1", "@stitches/react": "^0.2.2",
"@types/uuid": "^8.3.0",
"browser-fs-access": "^0.17.3", "browser-fs-access": "^0.17.3",
"framer-motion": "^4.1.16", "framer-motion": "^4.1.17",
"gtag": "^1.0.1", "gtag": "^1.0.1",
"idb-keyval": "^5.0.6", "idb-keyval": "^5.0.6",
"ismobilejs": "^1.1.1", "ismobilejs": "^1.1.1",
"next": "10.2.0", "monaco-editor": "^0.25.2",
"next": "latest",
"next-auth": "^3.27.0", "next-auth": "^3.27.0",
"next-pwa": "^5.2.21", "next-pwa": "^5.2.21",
"perfect-freehand": "^0.4.9", "perfect-freehand": "^0.4.9",
"prettier": "^2.3.0", "react": "^17.0.2",
"react": "17.0.2", "react-dom": "^17.0.2",
"react-dom": "17.0.2",
"react-feather": "^2.0.9", "react-feather": "^2.0.9",
"react-use-gesture": "^9.1.3", "react-use-gesture": "^9.1.3",
"uuid": "^8.3.2" "uuid": "^8.3.2"
}, },
"devDependencies": { "devDependencies": {
"@types/next": "^9.0.0", "@testing-library/react": "^11.2.5",
"@types/react": "^17.0.5", "@types/jest": "^26.0.23",
"@types/react-dom": "^17.0.3", "@types/node": "^14.14.25",
"@types/uuid": "^8.3.0", "@types/react": "^17.0.1",
"@typescript-eslint/eslint-plugin": "^4.14.2",
"@typescript-eslint/parser": "^4.14.2",
"babel-jest": "^27.0.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", "jest": "^27.0.4",
"monaco-editor": "^0.24.0", "jest-watch-typeahead": "^0.6.1",
"typescript": "^4.2.4" "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 useGtag from 'hooks/useGtag'
import { AppProps } from 'next/app' import { AppProps } from 'next/app'
import { globalStyles } from 'styles' import { globalStyles } from 'styles'
import 'styles/globals.css'
import { Provider } from 'next-auth/client' import { Provider } from 'next-auth/client'
import 'styles/globals.css'
function MyApp({ Component, pageProps }: AppProps) { function MyApp({ Component, pageProps }: AppProps): JSX.Element {
globalStyles() globalStyles()
useGtag() 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 { dark, getCssString } from 'styles'
import { GA_TRACKING_ID } from 'utils/gtag' import { GA_TRACKING_ID } from 'utils/gtag'
class MyDocument extends NextDocument { class MyDocument extends NextDocument {
static async getInitialProps(ctx) { static async getInitialProps(ctx: DocumentContext): Promise<{
styles: JSX.Element
html: string
head?: JSX.Element[]
}> {
try { try {
const initialProps = await NextDocument.getInitialProps(ctx) const initialProps = await NextDocument.getInitialProps(ctx)
@ -22,10 +32,11 @@ class MyDocument extends NextDocument {
} catch (e) { } catch (e) {
console.error(e.message) console.error(e.message)
} finally { } finally {
null
} }
} }
render() { render(): JSX.Element {
return ( return (
<Html lang="en"> <Html lang="en">
<Head> <Head>

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,6 @@
import { GetServerSideProps, NextApiRequest, NextApiResponse } from 'next' import { signOut } from 'next-auth/client'
import { signOut, signout } from 'next-auth/client'
export default function SignOut() { export default function SignOut(): JSX.Element {
return ( return (
<div> <div>
<button onClick={() => signOut()}>Sign Out</button> <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 { GetServerSideProps } from 'next'
import React from 'react' import React from 'react'
export default function Sponsorware() { export default function Sponsorware(): JSX.Element {
const [session, loading] = useSession() const [session, loading] = useSession()
return ( return (
@ -27,7 +27,7 @@ export default function Sponsorware() {
<li>only available for my sponsors</li> <li>only available for my sponsors</li>
</ul> </ul>
<p> <p>
If you'd like to try it out,{' '} If you&apos;d like to try it out,{' '}
<a <a
href="https://github.com/sponsors/steveruizok" href="https://github.com/sponsors/steveruizok"
target="_blank" target="_blank"
@ -45,7 +45,7 @@ export default function Sponsorware() {
</Button> </Button>
<Detail> <Detail>
Signed in as {session?.user?.name} ({session?.user?.email}), but 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 /> <br />
Something wrong? Try <a href="/">reloading the page</a> or DM me Something wrong? Try <a href="/">reloading the page</a> or DM me
on <a href="https://twitter.com/steveruizok">Twitter</a>. on <a href="https://twitter.com/steveruizok">Twitter</a>.
@ -67,7 +67,7 @@ export default function Sponsorware() {
export const getServerSideProps: GetServerSideProps = async (context) => { export const getServerSideProps: GetServerSideProps = async (context) => {
const session = await getSession(context) const session = await getSession(context)
if (!!session?.user) { if (session?.user) {
context.res.setHeader('Location', `/`) context.res.setHeader('Location', `/`)
context.res.statusCode = 307 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 { Data, Shape } from 'types'
import { getCommonBounds, getSelectedIds, getSelectedShapes } from 'utils/utils' import { getCommonBounds, getSelectedShapes } from 'utils/utils'
import state from './state' import state from './state'
class Clipboard { class Clipboard {
@ -99,10 +99,9 @@ class Clipboard {
} }
static copyStringToClipboard(string: string) { static copyStringToClipboard(string: string) {
let textarea: HTMLTextAreaElement
let result: boolean | null let result: boolean | null
textarea = document.createElement('textarea') const textarea = document.createElement('textarea')
textarea.setAttribute('position', 'fixed') textarea.setAttribute('position', 'fixed')
textarea.setAttribute('top', '0') textarea.setAttribute('top', '0')
textarea.setAttribute('readonly', 'true') textarea.setAttribute('readonly', 'true')

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,7 +2,23 @@ import { Bounds } from 'types'
import Vector, { Point } from './vector' import Vector, { Point } from './vector'
export default class Utils { 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), const p0e = Vector.add(p0, n0),
p1e = Vector.add(p1, n1), p1e = Vector.add(p1, n1),
m0 = (p0e.y - p0.y) / (p0e.x - p0.x), m0 = (p0e.y - p0.y) / (p0e.x - p0.x),
@ -20,7 +36,7 @@ export default class Utils {
r0: number, r0: number,
P: Point | Vector, P: Point | Vector,
side: number side: number
) { ): Vector {
const v0 = Vector.cast(A) const v0 = Vector.cast(A)
const v1 = Vector.cast(P) const v1 = Vector.cast(P)
const B = Vector.lrp(v0, v1, 0.5), 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) 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 max = Math.PI * 2
const da = (b - a) % max const da = (b - a) % max
return ((2 * da) % max) - da 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)) 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 ( return (
a * (1 - t) * (1 - t) * (1 - t) + a * (1 - t) * (1 - t) * (1 - t) +
3 * b * 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), const minX = Math.min(a.minX, b.minX),
minY = Math.min(a.minY, b.minY), minY = Math.min(a.minY, b.minY),
maxX = Math.max(a.maxX, b.maxX), maxX = Math.max(a.maxX, b.maxX),
@ -137,7 +153,7 @@ export default class Utils {
return { minX, minY, maxX, maxY, width, height } return { minX, minY, maxX, maxY, width, height }
} }
static getCommonBounds(...b: Bounds[]) { static getCommonBounds(...b: Bounds[]): Bounds {
if (b.length < 2) return b[0] if (b.length < 2) return b[0]
let bounds = 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.x = v.x
this.y = v.y this.y = v.y
return this
} }
copy() { copy(): Vector {
return new Vector(this) return new Vector(this)
} }
clone() { clone(): Vector {
return this.copy() return this.copy()
} }
toArray() { toArray(): number[] {
return [this.x, this.y] return [this.x, this.y]
} }
add(b: Vector) { add(b: Vector): Vector {
this.x += b.x this.x += b.x
this.y += b.y this.y += b.y
return this return this
} }
static add(a: Vector, b: Vector) { static add(a: Vector, b: Vector): Vector {
const n = new Vector(a) const n = new Vector(a)
n.x += b.x n.x += b.x
n.y += b.y n.y += b.y
return n return n
} }
sub(b: Vector) { sub(b: Vector): Vector {
this.x -= b.x this.x -= b.x
this.y -= b.y this.y -= b.y
return this return this
} }
static sub(a: Vector, b: Vector) { static sub(a: Vector, b: Vector): Vector {
const n = new Vector(a) const n = new Vector(a)
n.x -= b.x n.x -= b.x
n.y -= b.y n.y -= b.y
@ -71,7 +72,7 @@ export default class Vector {
mul(b: number): Vector mul(b: number): Vector
mul(b: Vector): Vector mul(b: Vector): Vector
mul(b: Vector | number) { mul(b: Vector | number): Vector {
if (b instanceof Vector) { if (b instanceof Vector) {
this.x *= b.x this.x *= b.x
this.y *= b.y this.y *= b.y
@ -82,17 +83,17 @@ export default class Vector {
return this return this
} }
mulScalar(b: number) { mulScalar(b: number): Vector {
return this.mul(b) return this.mul(b)
} }
static mulScalar(a: Vector, b: number) { static mulScalar(a: Vector, b: number): Vector {
return Vector.mul(a, b) return Vector.mul(a, b)
} }
static mul(a: Vector, b: number): Vector static mul(a: Vector, b: number): Vector
static mul(a: Vector, b: Vector): 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) const n = new Vector(a)
if (b instanceof Vector) { if (b instanceof Vector) {
n.x *= b.x n.x *= b.x
@ -106,7 +107,7 @@ export default class Vector {
div(b: number): Vector div(b: number): Vector
div(b: Vector): Vector div(b: Vector): Vector
div(b: Vector | number) { div(b: Vector | number): Vector {
if (b instanceof Vector) { if (b instanceof Vector) {
if (b.x) { if (b.x) {
this.x /= 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: number): Vector
static div(a: Vector, b: Vector): 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) const n = new Vector(a)
if (b instanceof Vector) { if (b instanceof Vector) {
if (b.x) n.x /= b.x if (b.x) n.x /= b.x
@ -139,112 +140,112 @@ export default class Vector {
return n return n
} }
divScalar(b: number) { divScalar(b: number): Vector {
return this.div(b) return this.div(b)
} }
static divScalar(a: Vector, b: number) { static divScalar(a: Vector, b: number): Vector {
return Vector.div(a, b) return Vector.div(a, b)
} }
vec(b: Vector) { vec(b: Vector): Vector {
const { x, y } = this const { x, y } = this
this.x = b.x - x this.x = b.x - x
this.y = b.y - y this.y = b.y - y
return this return this
} }
static vec(a: Vector, b: Vector) { static vec(a: Vector, b: Vector): Vector {
const n = new Vector(a) const n = new Vector(a)
n.x = b.x - a.x n.x = b.x - a.x
n.y = b.y - a.y n.y = b.y - a.y
return n return n
} }
pry(b: Vector) { pry(b: Vector): number {
return this.dpr(b) / b.len() 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() return a.dpr(b) / b.len()
} }
dpr(b: Vector) { dpr(b: Vector): number {
return this.x * b.x + this.y * b.y 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) 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 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 return a.x * b.y - b.y * a.y
} }
tangent(b: Vector) { tangent(b: Vector): Vector {
return this.sub(b).uni() return this.sub(b).uni()
} }
static tangent(a: Vector, b: Vector) { static tangent(a: Vector, b: Vector): Vector {
const n = new Vector(a) const n = new Vector(a)
return n.sub(b).uni() return n.sub(b).uni()
} }
dist2(b: Vector) { dist2(b: Vector): number {
return this.sub(b).len2() return this.sub(b).len2()
} }
static dist2(a: Vector, b: Vector) { static dist2(a: Vector, b: Vector): number {
const n = new Vector(a) const n = new Vector(a)
return n.sub(b).len2() return n.sub(b).len2()
} }
dist(b: Vector) { dist(b: Vector): number {
return Math.hypot(b.y - this.y, b.x - this.x) 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) const n = new Vector(a)
return Math.hypot(b.y - n.y, b.x - n.x) 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) 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) const n = new Vector(a)
return Math.atan2(b.y - n.y, b.x - n.x) 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) 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) const n = new Vector(a)
return n.add(b).mul(0.5) return n.add(b).mul(0.5)
} }
rot(r: number) { rot(r: number): Vector {
const { x, y } = this const { x, y } = this
this.x = x * Math.cos(r) - y * Math.sin(r) this.x = x * Math.cos(r) - y * Math.sin(r)
this.y = x * Math.sin(r) + y * Math.cos(r) this.y = x * Math.sin(r) + y * Math.cos(r)
return this return this
} }
static rot(a: Vector, r: number) { static rot(a: Vector, r: number): Vector {
const n = new Vector(a) const n = new Vector(a)
n.x = a.x * Math.cos(r) - a.y * Math.sin(r) n.x = a.x * Math.cos(r) - a.y * Math.sin(r)
n.y = a.x * Math.sin(r) + a.y * Math.cos(r) n.y = a.x * Math.sin(r) + a.y * Math.cos(r)
return n return n
} }
rotAround(b: Vector, r: number) { rotAround(b: Vector, r: number): Vector {
const { x, y } = this const { x, y } = this
const s = Math.sin(r) const s = Math.sin(r)
const c = Math.cos(r) const c = Math.cos(r)
@ -258,7 +259,7 @@ export default class Vector {
return this 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 n = new Vector(a)
const s = Math.sin(r) const s = Math.sin(r)
const c = Math.cos(r) const c = Math.cos(r)
@ -272,173 +273,185 @@ export default class Vector {
return n return n
} }
lrp(b: Vector, t: number) { lrp(b: Vector, t: number): Vector {
const n = new Vector(this) const n = new Vector(this)
this.vec(b).mul(t).add(n) 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) const n = new Vector(a)
n.vec(b).mul(t).add(a) n.vec(b).mul(t).add(a)
return n return n
} }
nudge(b: Vector, d: number) { nudge(b: Vector, d: number): Vector {
this.add(b.mul(d)) 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) const n = new Vector(a)
return n.add(b.mul(d)) 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) 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) 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) const t = (Math.max(from, to) - from) / (to - from)
this.add(Vector.mul(this, 1 - t).add(Vector.mul(b, s))) this.add(Vector.mul(this, 1 - t).add(Vector.mul(b, s)))
return this 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 n = new Vector(a)
const t = (Math.max(from, to) - from) / (to - from) const t = (Math.max(from, to) - from) / (to - from)
n.add(Vector.mul(a, 1 - t).add(Vector.mul(b, s))) n.add(Vector.mul(a, 1 - t).add(Vector.mul(b, s)))
return n return n
} }
equals(b: Vector) { equals(b: Vector): boolean {
return this.x === b.x && this.y === b.y 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 return a.x === b.x && a.y === b.y
} }
abs() { abs(): Vector {
this.x = Math.abs(this.x) this.x = Math.abs(this.x)
this.y = Math.abs(this.y) this.y = Math.abs(this.y)
return this return this
} }
static abs(a: Vector) { static abs(a: Vector): Vector {
const n = new Vector(a) const n = new Vector(a)
n.x = Math.abs(n.x) n.x = Math.abs(n.x)
n.y = Math.abs(n.y) n.y = Math.abs(n.y)
return n return n
} }
len() { len(): number {
return Math.hypot(this.x, this.y) return Math.hypot(this.x, this.y)
} }
static len(a: Vector) { static len(a: Vector): number {
return Math.hypot(a.x, a.y) return Math.hypot(a.x, a.y)
} }
len2() { len2(): number {
return this.x * this.x + this.y * this.y 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 return a.x * a.x + a.y * a.y
} }
per() { per(): Vector {
const t = this.x const t = this.x
this.x = this.y this.x = this.y
this.y = -t this.y = -t
return this return this
} }
static per(a: Vector) { static per(a: Vector): Vector {
const n = new Vector(a) const n = new Vector(a)
n.x = n.y n.x = n.y
n.y = -a.x n.y = -a.x
return n return n
} }
neg() { neg(): Vector {
this.x *= -1 this.x *= -1
this.y *= -1 this.y *= -1
return this return this
} }
static neg(v: Vector) { static neg(v: Vector): Vector {
const n = new Vector(v) const n = new Vector(v)
n.x *= -1 n.x *= -1
n.y *= -1 n.y *= -1
return n return n
} }
uni() { uni(): Vector {
return this.div(this.len()) return this.div(this.len())
} }
static uni(v: Vector) { static uni(v: Vector): Vector {
const n = new Vector(v) const n = new Vector(v)
return n.div(n.len()) return n.div(n.len())
} }
normalize() { normalize(): Vector {
return this.uni() return this.uni()
} }
static normalize(v: Vector) { static normalize(v: Vector): Vector {
return Vector.uni(v) return Vector.uni(v)
} }
isLeft(center: Vector, b: Vector) { isLeft(center: Vector, b: Vector): number {
return ( return (
(center.x - this.x) * (b.y - this.y) - (b.x - this.x) * (center.y - b.y) (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) 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 v1 = Vector.vec(center, a)
const v2 = Vector.vec(center, b) const v2 = Vector.vec(center, b)
return Vector.ang(v1, v2) 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 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) return 'cast' in v ? v : new Vector(v)
} }
static from(v: Vector) { static from(v: Vector): Vector {
return new Vector(v) 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))) 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))) 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)) 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)) 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) return Vector.nearestPointOnLineSegment(this, p0, p1, clamp)
} }
@ -447,7 +460,7 @@ export default class Vector {
p0: Vector, p0: Vector,
p1: Vector, p1: Vector,
clamp = true clamp = true
) { ): Vector {
const delta = Vector.sub(p1, p0) const delta = Vector.sub(p1, p0)
const length = delta.len() const length = delta.len()
const u = Vector.div(delta, length) const u = Vector.div(delta, length)
@ -465,7 +478,7 @@ export default class Vector {
return pt return pt
} }
distanceToLineSegment(p0: Vector, p1: Vector, clamp = true) { distanceToLineSegment(p0: Vector, p1: Vector, clamp = true): number {
return Vector.distanceToLineSegment(this, p0, p1, clamp) return Vector.distanceToLineSegment(this, p0, p1, clamp)
} }
@ -474,7 +487,7 @@ export default class Vector {
p0: Vector, p0: Vector,
p1: Vector, p1: Vector,
clamp = true clamp = true
) { ): number {
return Vector.dist(a, Vector.nearestPointOnLineSegment(a, p0, p1, clamp)) 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 history from '../history'
import { AlignType, Data } from 'types' import { AlignType, Data } from 'types'
import { getCommonBounds, getPage, getSelectedShapes } from 'utils/utils' 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 { currentPageId } = data
const selectedShapes = getSelectedShapes(data) const selectedShapes = getSelectedShapes(data)
const entries = selectedShapes.map( const entries = selectedShapes.map(
@ -25,7 +25,7 @@ export default function alignCommand(data: Data, type: AlignType) {
switch (type) { switch (type) {
case AlignType.Top: { case AlignType.Top: {
for (let id in boundsForShapes) { for (const id in boundsForShapes) {
const shape = shapes[id] const shape = shapes[id]
getShapeUtils(shape).translateTo(shape, [ getShapeUtils(shape).translateTo(shape, [
shape.point[0], shape.point[0],
@ -35,7 +35,7 @@ export default function alignCommand(data: Data, type: AlignType) {
break break
} }
case AlignType.CenterVertical: { case AlignType.CenterVertical: {
for (let id in boundsForShapes) { for (const id in boundsForShapes) {
const shape = shapes[id] const shape = shapes[id]
getShapeUtils(shape).translateTo(shape, [ getShapeUtils(shape).translateTo(shape, [
shape.point[0], shape.point[0],
@ -45,7 +45,7 @@ export default function alignCommand(data: Data, type: AlignType) {
break break
} }
case AlignType.Bottom: { case AlignType.Bottom: {
for (let id in boundsForShapes) { for (const id in boundsForShapes) {
const shape = shapes[id] const shape = shapes[id]
getShapeUtils(shape).translateTo(shape, [ getShapeUtils(shape).translateTo(shape, [
shape.point[0], shape.point[0],
@ -55,7 +55,7 @@ export default function alignCommand(data: Data, type: AlignType) {
break break
} }
case AlignType.Left: { case AlignType.Left: {
for (let id in boundsForShapes) { for (const id in boundsForShapes) {
const shape = shapes[id] const shape = shapes[id]
getShapeUtils(shape).translateTo(shape, [ getShapeUtils(shape).translateTo(shape, [
commonBounds.minX, commonBounds.minX,
@ -65,7 +65,7 @@ export default function alignCommand(data: Data, type: AlignType) {
break break
} }
case AlignType.CenterHorizontal: { case AlignType.CenterHorizontal: {
for (let id in boundsForShapes) { for (const id in boundsForShapes) {
const shape = shapes[id] const shape = shapes[id]
getShapeUtils(shape).translateTo(shape, [ getShapeUtils(shape).translateTo(shape, [
midX - boundsForShapes[id].width / 2, midX - boundsForShapes[id].width / 2,
@ -75,7 +75,7 @@ export default function alignCommand(data: Data, type: AlignType) {
break break
} }
case AlignType.Right: { case AlignType.Right: {
for (let id in boundsForShapes) { for (const id in boundsForShapes) {
const shape = shapes[id] const shape = shapes[id]
getShapeUtils(shape).translateTo(shape, [ getShapeUtils(shape).translateTo(shape, [
commonBounds.maxX - boundsForShapes[id].width, commonBounds.maxX - boundsForShapes[id].width,
@ -88,7 +88,7 @@ export default function alignCommand(data: Data, type: AlignType) {
}, },
undo(data) { undo(data) {
const { shapes } = getPage(data, currentPageId) const { shapes } = getPage(data, currentPageId)
for (let id in boundsForShapes) { for (const id in boundsForShapes) {
const shape = shapes[id] const shape = shapes[id]
const initialBounds = boundsForShapes[id] const initialBounds = boundsForShapes[id]
getShapeUtils(shape).translateTo(shape, [ getShapeUtils(shape).translateTo(shape, [

View file

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

View file

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

View file

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

View file

@ -5,7 +5,7 @@ import { uniqueId } from 'utils/utils'
import { current } from 'immer' import { current } from 'immer'
import storage from 'state/storage' 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) const snapshot = getSnapshot(data)
history.execute( history.execute(

View file

@ -2,10 +2,9 @@ import Command from './command'
import history from '../history' import history from '../history'
import { Data } from 'types' import { Data } from 'types'
import { current } from 'immer' import { current } from 'immer'
import vec from 'utils/vec'
import storage from 'state/storage' 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) const snapshot = getSnapshot(data, pageId)
history.execute( history.execute(

View file

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

View file

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

View file

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

View file

@ -4,7 +4,7 @@ import { Data, DrawShape } from 'types'
import { getPage, setSelectedIds } from 'utils/utils' import { getPage, setSelectedIds } from 'utils/utils'
import { current } from 'immer' 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 const restoreShape = getPage(current(data)).shapes[id] as DrawShape
history.execute( history.execute(

View file

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

View file

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

View file

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

View file

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

View file

@ -3,22 +3,19 @@ import history from '../history'
import { Data } from 'types' import { Data } from 'types'
import { getPage } from 'utils/utils' import { getPage } from 'utils/utils'
import { HandleSnapshot } from 'state/sessions/handle-session' import { HandleSnapshot } from 'state/sessions/handle-session'
import { getShapeUtils } from 'lib/shape-utils' import { getShapeUtils } from 'state/shape-utils'
import vec from 'utils/vec'
export default function handleCommand( export default function handleCommand(
data: Data, data: Data,
before: HandleSnapshot, before: HandleSnapshot,
after: HandleSnapshot after: HandleSnapshot
) { ): void {
history.execute( history.execute(
data, data,
new Command({ new Command({
name: 'moved_handle', name: 'moved_handle',
category: 'canvas', category: 'canvas',
do(data, isInitial) { do(data) {
// if (isInitial) return
const { initialShape, currentPageId } = after const { initialShape, currentPageId } = after
const page = getPage(data, currentPageId) const page = getPage(data, currentPageId)
@ -27,18 +24,6 @@ export default function handleCommand(
getShapeUtils(shape) getShapeUtils(shape)
.onHandleChange(shape, initialShape.handles) .onHandleChange(shape, initialShape.handles)
.onSessionComplete(shape) .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) { undo(data) {
const { initialShape, currentPageId } = before const { initialShape, currentPageId } = before

View file

@ -6,16 +6,13 @@ import {
getPage, getPage,
getPageState, getPageState,
getSelectedIds, getSelectedIds,
getSelectedShapes,
getTopParentId,
setToArray, setToArray,
uniqueArray, uniqueArray,
} from 'utils/utils' } from 'utils/utils'
import { getShapeUtils } from 'lib/shape-utils' import { getShapeUtils } from 'state/shape-utils'
import vec from 'utils/vec'
import storage from 'state/storage' 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 { currentPageId: oldPageId } = data
const oldPage = getPage(data) const oldPage = getPage(data)
const selectedIds = setToArray(getSelectedIds(data)) const selectedIds = setToArray(getSelectedIds(data))

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