big cleanup
This commit is contained in:
parent
daa44f9911
commit
864ded959a
161 changed files with 2918 additions and 5749 deletions
4
.babelrc
4
.babelrc
|
@ -1,7 +1,3 @@
|
||||||
{
|
{
|
||||||
"env": {
|
|
||||||
"test": {
|
|
||||||
"presets": ["next/babel"]
|
"presets": ["next/babel"]
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
3
.eslintignore
Normal file
3
.eslintignore
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
**/node_modules/*
|
||||||
|
**/out/*
|
||||||
|
**/.next/*
|
49
.eslintrc.json
Normal file
49
.eslintrc.json
Normal 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
21
LICENSE
|
@ -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.
|
|
35
README.md
35
README.md
|
@ -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.
|
|
||||||
|
|
|
@ -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'))
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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,
|
||||||
})
|
})
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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',
|
||||||
})
|
})
|
||||||
|
|
|
@ -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(() => {
|
||||||
|
|
|
@ -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 }) => {
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 }
|
|
||||||
}
|
|
||||||
|
|
|
@ -61,7 +61,7 @@ export default function AlignDistribute({
|
||||||
}: {
|
}: {
|
||||||
hasTwoOrMore: boolean
|
hasTwoOrMore: boolean
|
||||||
hasThreeOrMore: boolean
|
hasThreeOrMore: boolean
|
||||||
}) {
|
}): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
|
|
@ -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) => (
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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
2
decs.d.ts
vendored
|
@ -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[]>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'
|
||||||
|
|
||||||
|
|
|
@ -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'
|
||||||
|
|
||||||
|
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
19
jest.config.js
Normal 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',
|
||||||
|
},
|
||||||
|
}
|
|
@ -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
|
|
|
@ -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
|
|
||||||
)
|
|
82
package.json
82
package.json
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export default function CreateError() {
|
export default function CreateError(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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'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'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
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
1
public/workbox-ea903bce.js.map
Normal file
1
public/workbox-ea903bce.js.map
Normal file
File diff suppressed because one or more lines are too long
|
@ -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')
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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]
|
||||||
}
|
}
|
|
@ -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
|
||||||
}
|
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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,
|
||||||
])
|
])
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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]
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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, [
|
||||||
|
|
|
@ -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({
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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, [
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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) => ({
|
||||||
|
|
|
@ -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({
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -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],
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
Loading…
Reference in a new issue