big cleanup
This commit is contained in:
parent
daa44f9911
commit
864ded959a
161 changed files with 2918 additions and 5749 deletions
6
.babelrc
6
.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
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
|
||||
|
||||
[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.
|
||||
|
||||
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
||||
A tiny little drawing app by [steveruizok](https://twitter.com/steveruizok).
|
||||
|
|
|
@ -15,7 +15,7 @@ import CornerHandle from './corner-handle'
|
|||
import EdgeHandle from './edge-handle'
|
||||
import RotateHandle from './rotate-handle'
|
||||
|
||||
export default function Bounds() {
|
||||
export default function Bounds(): JSX.Element {
|
||||
const isBrushing = useSelector((s) => s.isIn('brushSelecting'))
|
||||
|
||||
const isSelecting = useSelector((s) => s.isIn('selecting'))
|
||||
|
|
|
@ -24,7 +24,7 @@ function handlePointerUp(e: React.PointerEvent<SVGRectElement>) {
|
|||
state.send('STOPPED_POINTING', inputs.pointerUp(e))
|
||||
}
|
||||
|
||||
export default function BoundsBg() {
|
||||
export default function BoundsBg(): JSX.Element {
|
||||
const rBounds = useRef<SVGRectElement>(null)
|
||||
|
||||
const bounds = useSelector((state) => state.values.selectedBounds)
|
||||
|
|
|
@ -7,7 +7,7 @@ export default function CenterHandle({
|
|||
}: {
|
||||
bounds: Bounds
|
||||
isLocked: boolean
|
||||
}) {
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<StyledBounds
|
||||
x={-1}
|
||||
|
|
|
@ -10,7 +10,7 @@ export default function CornerHandle({
|
|||
size: number
|
||||
bounds: Bounds
|
||||
corner: Corner
|
||||
}) {
|
||||
}): JSX.Element {
|
||||
const events = useBoundsEvents(corner)
|
||||
|
||||
const isTop = corner === Corner.TopLeft || corner === Corner.TopRight
|
||||
|
|
|
@ -10,7 +10,7 @@ export default function EdgeHandle({
|
|||
size: number
|
||||
bounds: Bounds
|
||||
edge: Edge
|
||||
}) {
|
||||
}): JSX.Element {
|
||||
const events = useBoundsEvents(edge)
|
||||
|
||||
const isHorizontal = edge === Edge.Top || edge === Edge.Bottom
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import useHandleEvents from 'hooks/useHandleEvents'
|
||||
import { getShapeUtils } from 'lib/shape-utils'
|
||||
import { getShapeUtils } from 'state/shape-utils'
|
||||
import { useRef } from 'react'
|
||||
import { useSelector } from 'state'
|
||||
import styled from 'styles'
|
||||
import { deepCompareArrays, getPage } from 'utils/utils'
|
||||
import vec from 'utils/vec'
|
||||
|
||||
export default function Handles() {
|
||||
export default function Handles(): JSX.Element {
|
||||
const selectedIds = useSelector(
|
||||
(s) => Array.from(s.values.selectedIds.values()),
|
||||
deepCompareArrays
|
||||
|
|
|
@ -8,7 +8,7 @@ export default function Rotate({
|
|||
}: {
|
||||
bounds: Bounds
|
||||
size: number
|
||||
}) {
|
||||
}): JSX.Element {
|
||||
const events = useHandleEvents('rotate')
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { useSelector } from "state"
|
||||
import styled from "styles"
|
||||
import { useSelector } from 'state'
|
||||
import styled from 'styles'
|
||||
|
||||
export default function Brush() {
|
||||
export default function Brush(): JSX.Element {
|
||||
const brush = useSelector(({ data }) => data.brush)
|
||||
|
||||
if (!brush) return null
|
||||
|
@ -16,8 +16,8 @@ export default function Brush() {
|
|||
)
|
||||
}
|
||||
|
||||
const BrushRect = styled("rect", {
|
||||
fill: "$brushFill",
|
||||
stroke: "$brushStroke",
|
||||
const BrushRect = styled('rect', {
|
||||
fill: '$brushFill',
|
||||
stroke: '$brushStroke',
|
||||
zStrokeWidth: 1,
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import styled from 'styles'
|
||||
import state, { useSelector } from 'state'
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import { useSelector } from 'state'
|
||||
import React, { useRef } from 'react'
|
||||
import useZoomEvents from 'hooks/useZoomEvents'
|
||||
import useCamera from 'hooks/useCamera'
|
||||
import Defs from './defs'
|
||||
|
@ -8,12 +8,11 @@ import Page from './page'
|
|||
import Brush from './brush'
|
||||
import Bounds from './bounds/bounding-box'
|
||||
import BoundsBg from './bounds/bounds-bg'
|
||||
import Selected from './selected'
|
||||
import Handles from './bounds/handles'
|
||||
import useCanvasEvents from 'hooks/useCanvasEvents'
|
||||
import ContextMenu from './context-menu/context-menu'
|
||||
|
||||
export default function Canvas() {
|
||||
export default function Canvas(): JSX.Element {
|
||||
const rCanvas = useRef<SVGSVGElement>(null)
|
||||
const rGroup = useRef<SVGGElement>(null)
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import * as _ContextMenu from '@radix-ui/react-context-menu'
|
||||
import * as _Dropdown from '@radix-ui/react-dropdown-menu'
|
||||
import styled from 'styles'
|
||||
import {
|
||||
IconWrapper,
|
||||
|
@ -79,7 +78,7 @@ export default function ContextMenu({
|
|||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
}): JSX.Element {
|
||||
const selectedShapes = useSelector(
|
||||
(s) => getSelectedShapes(s.data),
|
||||
deepCompareArrays
|
||||
|
@ -357,7 +356,6 @@ function SubMenu({
|
|||
}
|
||||
|
||||
function AlignDistributeSubMenu({
|
||||
hasTwoOrMore,
|
||||
hasThreeOrMore,
|
||||
}: {
|
||||
hasTwoOrMore: boolean
|
||||
|
@ -474,31 +472,6 @@ function MoveToPageMenu() {
|
|||
)
|
||||
}
|
||||
|
||||
const StyledDialogContent = styled(_Dropdown.Content, {
|
||||
// position: 'fixed',
|
||||
// top: '50%',
|
||||
// left: '50%',
|
||||
// transform: 'translate(-50%, -50%)',
|
||||
// minWidth: 200,
|
||||
// maxWidth: 'fit-content',
|
||||
// maxHeight: '85vh',
|
||||
// marginTop: '-5vh',
|
||||
minWidth: 128,
|
||||
backgroundColor: '$panel',
|
||||
borderRadius: '4px',
|
||||
overflow: 'hidden',
|
||||
pointerEvents: 'all',
|
||||
userSelect: 'none',
|
||||
zIndex: 200,
|
||||
padding: 2,
|
||||
border: '1px solid $panel',
|
||||
boxShadow: '0px 2px 4px rgba(0,0,0,.2)',
|
||||
|
||||
'&:focus': {
|
||||
outline: 'none',
|
||||
},
|
||||
})
|
||||
|
||||
const StyledArrow = styled(_ContextMenu.Arrow, {
|
||||
fill: 'white',
|
||||
})
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useEffect, useRef } from 'react'
|
||||
import styled from 'styles'
|
||||
|
||||
export default function Cursor() {
|
||||
export default function Cursor(): JSX.Element {
|
||||
const rCursor = useRef<SVGSVGElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { getShapeStyle } from 'lib/shape-styles'
|
||||
import { getShapeUtils } from 'lib/shape-utils'
|
||||
import { getShapeStyle } from 'state/shape-styles'
|
||||
import { getShapeUtils } from 'state/shape-utils'
|
||||
import React, { memo } from 'react'
|
||||
import { useSelector } from 'state'
|
||||
import { deepCompareArrays, getCurrentCamera, getPage } from 'utils/utils'
|
||||
import { DotCircle, Handle } from './misc'
|
||||
|
||||
export default function Defs() {
|
||||
export default function Defs(): JSX.Element {
|
||||
const zoom = useSelector((s) => getCurrentCamera(s.data).zoom)
|
||||
|
||||
const currentPageShapeIds = useSelector(({ data }) => {
|
||||
|
|
|
@ -1,13 +1,8 @@
|
|||
import { getShapeUtils } from 'lib/shape-utils'
|
||||
import state, { useSelector } from 'state'
|
||||
import { Bounds, GroupShape, PageState } from 'types'
|
||||
import { getShapeUtils } from 'state/shape-utils'
|
||||
import { useSelector } from 'state'
|
||||
import { Bounds, PageState } from 'types'
|
||||
import { boundsCollide, boundsContain } from 'utils/bounds'
|
||||
import {
|
||||
deepCompareArrays,
|
||||
getPage,
|
||||
getViewport,
|
||||
screenToWorld,
|
||||
} from 'utils/utils'
|
||||
import { deepCompareArrays, getPage, getViewport } from 'utils/utils'
|
||||
import Shape from './shape'
|
||||
|
||||
/*
|
||||
|
@ -20,7 +15,7 @@ const noOffset = [0, 0]
|
|||
|
||||
const viewportCache = new WeakMap<PageState, Bounds>()
|
||||
|
||||
export default function Page() {
|
||||
export default function Page(): JSX.Element {
|
||||
const currentPageShapeIds = useSelector((s) => {
|
||||
const page = getPage(s.data)
|
||||
const pageState = s.data.pageStates[page.id]
|
||||
|
|
|
@ -6,13 +6,10 @@ import {
|
|||
getSelectedIds,
|
||||
setToArray,
|
||||
} from 'utils/utils'
|
||||
import { getShapeUtils } from 'lib/shape-utils'
|
||||
import useShapeEvents from 'hooks/useShapeEvents'
|
||||
import { memo, useRef } from 'react'
|
||||
import { ShapeType } from 'types'
|
||||
import vec from 'utils/vec'
|
||||
import { getShapeUtils } from 'state/shape-utils'
|
||||
import { memo } from 'react'
|
||||
|
||||
export default function Selected() {
|
||||
export default function Selected(): JSX.Element {
|
||||
const currentSelectedShapeIds = useSelector(
|
||||
({ data }) => setToArray(getSelectedIds(data)),
|
||||
deepCompareArrays
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import React, { useRef, memo, useEffect } from 'react'
|
||||
import state, { useSelector } from 'state'
|
||||
import { useSelector } from 'state'
|
||||
import styled from 'styles'
|
||||
import { getShapeUtils } from 'lib/shape-utils'
|
||||
import { getBoundsCenter, getPage, isMobile } from 'utils/utils'
|
||||
import { ShapeStyles, ShapeType, Shape as _Shape } from 'types'
|
||||
import { getShapeUtils } from 'state/shape-utils'
|
||||
import { getPage, isMobile } from 'utils/utils'
|
||||
import { Shape as _Shape } from 'types'
|
||||
import useShapeEvents from 'hooks/useShapeEvents'
|
||||
import vec from 'utils/vec'
|
||||
import { getShapeStyle } from 'lib/shape-styles'
|
||||
import ContextMenu from 'components/canvas/context-menu/context-menu'
|
||||
import { getShapeStyle } from 'state/shape-styles'
|
||||
|
||||
const isMobileDevice = isMobile()
|
||||
|
||||
|
@ -17,7 +16,7 @@ interface ShapeProps {
|
|||
parentPoint: number[]
|
||||
}
|
||||
|
||||
function Shape({ id, isSelecting, parentPoint }: ShapeProps) {
|
||||
function Shape({ id, isSelecting, parentPoint }: ShapeProps): JSX.Element {
|
||||
const rGroup = useRef<SVGGElement>(null)
|
||||
const rFocusable = useRef<HTMLTextAreaElement>(null)
|
||||
|
||||
|
@ -122,12 +121,7 @@ interface RealShapeProps {
|
|||
isEditing: boolean
|
||||
}
|
||||
|
||||
const RealShape = memo(function RealShape({
|
||||
id,
|
||||
shape,
|
||||
style,
|
||||
isParent,
|
||||
}: RealShapeProps) {
|
||||
const RealShape = memo(function RealShape({ id, isParent }: RealShapeProps) {
|
||||
return <StyledShape as="use" data-shy={isParent} href={'#' + id} />
|
||||
})
|
||||
|
||||
|
@ -217,25 +211,25 @@ const StyledGroup = styled('g', {
|
|||
],
|
||||
})
|
||||
|
||||
function Label({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<text
|
||||
y={4}
|
||||
x={4}
|
||||
fontSize={12}
|
||||
fill="black"
|
||||
stroke="none"
|
||||
alignmentBaseline="text-before-edge"
|
||||
pointerEvents="none"
|
||||
>
|
||||
{children}
|
||||
</text>
|
||||
)
|
||||
}
|
||||
// function Label({ children }: { children: React.ReactNode }) {
|
||||
// return (
|
||||
// <text
|
||||
// y={4}
|
||||
// x={4}
|
||||
// fontSize={12}
|
||||
// fill="black"
|
||||
// stroke="none"
|
||||
// alignmentBaseline="text-before-edge"
|
||||
// pointerEvents="none"
|
||||
// >
|
||||
// {children}
|
||||
// </text>
|
||||
// )
|
||||
// }
|
||||
|
||||
function pp(n: number[]) {
|
||||
return '[' + n.map((v) => v.toFixed(1)).join(', ') + ']'
|
||||
}
|
||||
// function pp(n: number[]) {
|
||||
// return '[' + n.map((v) => v.toFixed(1)).join(', ') + ']'
|
||||
// }
|
||||
|
||||
export { HoverIndicator }
|
||||
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
import styled from "styles"
|
||||
import styled from 'styles'
|
||||
|
||||
export default function CodeDocs({ isHidden }: { isHidden: boolean }) {
|
||||
export default function CodeDocs({
|
||||
isHidden,
|
||||
}: {
|
||||
isHidden: boolean
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<StyledDocs isHidden={isHidden}>
|
||||
<h2>Docs</h2>
|
||||
|
@ -8,94 +12,94 @@ export default function CodeDocs({ isHidden }: { isHidden: boolean }) {
|
|||
)
|
||||
}
|
||||
|
||||
const StyledDocs = styled("div", {
|
||||
position: "absolute",
|
||||
backgroundColor: "$panel",
|
||||
const StyledDocs = styled('div', {
|
||||
position: 'absolute',
|
||||
backgroundColor: '$panel',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
padding: 16,
|
||||
font: "$docs",
|
||||
overflowY: "scroll",
|
||||
userSelect: "none",
|
||||
font: '$docs',
|
||||
overflowY: 'scroll',
|
||||
userSelect: 'none',
|
||||
paddingBottom: 64,
|
||||
|
||||
variants: {
|
||||
isHidden: {
|
||||
true: {
|
||||
visibility: "hidden",
|
||||
visibility: 'hidden',
|
||||
},
|
||||
false: {
|
||||
visibility: "visible",
|
||||
visibility: 'visible',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"& ol": {},
|
||||
'& ol': {},
|
||||
|
||||
"& li": {
|
||||
'& li': {
|
||||
marginTop: 8,
|
||||
marginBottom: 4,
|
||||
},
|
||||
|
||||
"& code": {
|
||||
font: "$mono",
|
||||
'& code': {
|
||||
font: '$mono',
|
||||
},
|
||||
|
||||
"& hr": {
|
||||
margin: "32px 0",
|
||||
borderColor: "$muted",
|
||||
'& hr': {
|
||||
margin: '32px 0',
|
||||
borderColor: '$muted',
|
||||
},
|
||||
|
||||
"& h2": {
|
||||
margin: "24px 0px",
|
||||
'& h2': {
|
||||
margin: '24px 0px',
|
||||
},
|
||||
|
||||
"& h3": {
|
||||
'& h3': {
|
||||
fontSize: 20,
|
||||
margin: "48px 0px 32px 0px",
|
||||
margin: '48px 0px 32px 0px',
|
||||
},
|
||||
|
||||
"& h3 > code": {
|
||||
'& h3 > code': {
|
||||
fontWeight: 600,
|
||||
font: "$monoheading",
|
||||
font: '$monoheading',
|
||||
},
|
||||
|
||||
"& h4": {
|
||||
margin: "32px 0px 0px 0px",
|
||||
'& h4': {
|
||||
margin: '32px 0px 0px 0px',
|
||||
},
|
||||
|
||||
"& h4 > code": {
|
||||
font: "$monoheading",
|
||||
'& h4 > code': {
|
||||
font: '$monoheading',
|
||||
fontSize: 16,
|
||||
userSelect: "all",
|
||||
userSelect: 'all',
|
||||
},
|
||||
|
||||
"& h4 > code > i": {
|
||||
'& h4 > code > i': {
|
||||
fontSize: 14,
|
||||
color: "$muted",
|
||||
color: '$muted',
|
||||
},
|
||||
|
||||
"& pre": {
|
||||
backgroundColor: "$bounds_bg",
|
||||
'& pre': {
|
||||
backgroundColor: '$bounds_bg',
|
||||
padding: 16,
|
||||
borderRadius: 4,
|
||||
userSelect: "all",
|
||||
margin: "24px 0",
|
||||
userSelect: 'all',
|
||||
margin: '24px 0',
|
||||
},
|
||||
|
||||
"& p > code, blockquote > code": {
|
||||
backgroundColor: "$bounds_bg",
|
||||
padding: "2px 4px",
|
||||
'& p > code, blockquote > code': {
|
||||
backgroundColor: '$bounds_bg',
|
||||
padding: '2px 4px',
|
||||
borderRadius: 2,
|
||||
color: "$code",
|
||||
color: '$code',
|
||||
},
|
||||
|
||||
"& blockquote": {
|
||||
backgroundColor: "rgba(144, 144, 144, .05)",
|
||||
'& blockquote': {
|
||||
backgroundColor: 'rgba(144, 144, 144, .05)',
|
||||
padding: 12,
|
||||
margin: "20px 0",
|
||||
margin: '20px 0',
|
||||
borderRadius: 8,
|
||||
},
|
||||
})
|
||||
|
|
|
@ -32,7 +32,7 @@ export default function CodeEditor({
|
|||
onChange,
|
||||
onSave,
|
||||
onKey,
|
||||
}: Props) {
|
||||
}: Props): JSX.Element {
|
||||
const { theme } = useTheme()
|
||||
const rEditor = useRef<IMonacoEditor>(null)
|
||||
const rMonaco = useRef<IMonaco>(null)
|
||||
|
|
|
@ -2,12 +2,11 @@
|
|||
import styled from 'styles'
|
||||
import { useStateDesigner } from '@state-designer/react'
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import { motion } from 'framer-motion'
|
||||
import state, { useSelector } from 'state'
|
||||
import { CodeFile } from 'types'
|
||||
import CodeDocs from './code-docs'
|
||||
import CodeEditor from './code-editor'
|
||||
import { generateFromCode } from 'lib/code/generate'
|
||||
import { generateFromCode } from 'state/code/generate'
|
||||
import * as Panel from '../panel'
|
||||
import { IconButton } from '../shared'
|
||||
import {
|
||||
|
@ -30,7 +29,7 @@ const getErrorLineAndColumn = (e: any) => {
|
|||
}
|
||||
}
|
||||
|
||||
export default function CodePanel() {
|
||||
export default function CodePanel(): JSX.Element {
|
||||
const rContainer = useRef<HTMLDivElement>(null)
|
||||
const isReadOnly = useSelector((s) => s.data.isReadOnly)
|
||||
const fileId = useSelector((s) => s.data.currentCodeFileId)
|
||||
|
|
|
@ -1,14 +1,8 @@
|
|||
import state, { useSelector } from "state"
|
||||
import styled from "styles"
|
||||
import {
|
||||
ControlType,
|
||||
NumberCodeControl,
|
||||
SelectCodeControl,
|
||||
TextCodeControl,
|
||||
VectorCodeControl,
|
||||
} from "types"
|
||||
import state, { useSelector } from 'state'
|
||||
import styled from 'styles'
|
||||
import { ControlType, NumberCodeControl, VectorCodeControl } from 'types'
|
||||
|
||||
export default function Control({ id }: { id: string }) {
|
||||
export default function Control({ id }: { id: string }): JSX.Element {
|
||||
const control = useSelector((s) => s.data.codeControls[id])
|
||||
|
||||
if (!control) return null
|
||||
|
@ -20,10 +14,6 @@ export default function Control({ id }: { id: string }) {
|
|||
<NumberControl {...control} />
|
||||
) : control.type === ControlType.Vector ? (
|
||||
<VectorControl {...control} />
|
||||
) : control.type === ControlType.Text ? (
|
||||
<TextControl {...control} />
|
||||
) : control.type === ControlType.Select ? (
|
||||
<SelectControl {...control} />
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
|
@ -39,7 +29,7 @@ function NumberControl({ id, min, max, step, value }: NumberCodeControl) {
|
|||
step={step}
|
||||
value={value}
|
||||
onChange={(e) =>
|
||||
state.send("CHANGED_CODE_CONTROL", {
|
||||
state.send('CHANGED_CODE_CONTROL', {
|
||||
[id]: Number(e.currentTarget.value),
|
||||
})
|
||||
}
|
||||
|
@ -51,7 +41,7 @@ function NumberControl({ id, min, max, step, value }: NumberCodeControl) {
|
|||
step={step}
|
||||
value={value}
|
||||
onChange={(e) =>
|
||||
state.send("CHANGED_CODE_CONTROL", {
|
||||
state.send('CHANGED_CODE_CONTROL', {
|
||||
[id]: Number(e.currentTarget.value),
|
||||
})
|
||||
}
|
||||
|
@ -70,7 +60,7 @@ function VectorControl({ id, value, isNormalized }: VectorCodeControl) {
|
|||
step={0.01}
|
||||
value={value[0]}
|
||||
onChange={(e) =>
|
||||
state.send("CHANGED_CODE_CONTROL", {
|
||||
state.send('CHANGED_CODE_CONTROL', {
|
||||
[id]: [Number(e.currentTarget.value), value[1]],
|
||||
})
|
||||
}
|
||||
|
@ -82,7 +72,7 @@ function VectorControl({ id, value, isNormalized }: VectorCodeControl) {
|
|||
step={0.01}
|
||||
value={value[0]}
|
||||
onChange={(e) =>
|
||||
state.send("CHANGED_CODE_CONTROL", {
|
||||
state.send('CHANGED_CODE_CONTROL', {
|
||||
[id]: [Number(e.currentTarget.value), value[1]],
|
||||
})
|
||||
}
|
||||
|
@ -94,7 +84,7 @@ function VectorControl({ id, value, isNormalized }: VectorCodeControl) {
|
|||
step={0.01}
|
||||
value={value[1]}
|
||||
onChange={(e) =>
|
||||
state.send("CHANGED_CODE_CONTROL", {
|
||||
state.send('CHANGED_CODE_CONTROL', {
|
||||
[id]: [value[0], Number(e.currentTarget.value)],
|
||||
})
|
||||
}
|
||||
|
@ -106,7 +96,7 @@ function VectorControl({ id, value, isNormalized }: VectorCodeControl) {
|
|||
step={0.01}
|
||||
value={value[1]}
|
||||
onChange={(e) =>
|
||||
state.send("CHANGED_CODE_CONTROL", {
|
||||
state.send('CHANGED_CODE_CONTROL', {
|
||||
[id]: [value[0], Number(e.currentTarget.value)],
|
||||
})
|
||||
}
|
||||
|
@ -115,28 +105,20 @@ function VectorControl({ id, value, isNormalized }: VectorCodeControl) {
|
|||
)
|
||||
}
|
||||
|
||||
function TextControl({}: TextCodeControl) {
|
||||
return <></>
|
||||
}
|
||||
const Inputs = styled('div', {
|
||||
display: 'flex',
|
||||
gap: '8px',
|
||||
height: '100%',
|
||||
|
||||
function SelectControl({}: SelectCodeControl) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
const Inputs = styled("div", {
|
||||
display: "flex",
|
||||
gap: "8px",
|
||||
height: "100%",
|
||||
|
||||
"& input": {
|
||||
font: "$ui",
|
||||
width: "64px",
|
||||
fontSize: "$1",
|
||||
border: "1px solid $inputBorder",
|
||||
backgroundColor: "$input",
|
||||
color: "$text",
|
||||
height: "100%",
|
||||
padding: "0px 6px",
|
||||
'& input': {
|
||||
font: '$ui',
|
||||
width: '64px',
|
||||
fontSize: '$1',
|
||||
border: '1px solid $inputBorder',
|
||||
backgroundColor: '$input',
|
||||
color: '$text',
|
||||
height: '100%',
|
||||
padding: '0px 6px',
|
||||
},
|
||||
"& input[type='range']": {
|
||||
padding: 0,
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
import styled from 'styles'
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import React, { useRef } from 'react'
|
||||
import state, { useSelector } from 'state'
|
||||
import { X, Code, PlayCircle } from 'react-feather'
|
||||
import { X, Code } from 'react-feather'
|
||||
import { IconButton } from 'components/shared'
|
||||
import * as Panel from '../panel'
|
||||
import Control from './control'
|
||||
import { deepCompareArrays } from 'utils/utils'
|
||||
|
||||
export default function ControlPanel() {
|
||||
export default function ControlPanel(): JSX.Element {
|
||||
const rContainer = useRef<HTMLDivElement>(null)
|
||||
const codeControls = useSelector(
|
||||
(state) => Object.keys(state.data.codeControls),
|
||||
|
|
|
@ -11,7 +11,7 @@ import PagePanel from './page-panel/page-panel'
|
|||
// import { useSelector } from 'state'
|
||||
// const CodePanel = dynamic(() => import('./code-panel/code-panel'))
|
||||
|
||||
export default function Editor() {
|
||||
export default function Editor(): JSX.Element {
|
||||
useKeyboardEvents()
|
||||
useLoadOnMount()
|
||||
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
import styled from 'styles'
|
||||
import * as ContextMenu from '@radix-ui/react-context-menu'
|
||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||
import * as Dialog from '@radix-ui/react-dialog'
|
||||
|
||||
import { IconWrapper, RowButton } from 'components/shared'
|
||||
import { CheckIcon, ChevronDownIcon, PlusIcon } from '@radix-ui/react-icons'
|
||||
import { CheckIcon, PlusIcon } from '@radix-ui/react-icons'
|
||||
import * as Panel from '../panel'
|
||||
import state, { useSelector } from 'state'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
|
||||
export default function PagePanel() {
|
||||
export default function PagePanel(): JSX.Element {
|
||||
const rIsOpen = useRef(false)
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
|
@ -189,30 +188,3 @@ const StyledContextMenuItem = styled(ContextMenu.Item, {
|
|||
},
|
||||
},
|
||||
})
|
||||
|
||||
const StyledOverlay = styled(Dialog.Overlay, {
|
||||
backgroundColor: 'rgba(0, 0, 0, .15)',
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
})
|
||||
|
||||
const StyledContent = styled(Dialog.Content, {
|
||||
position: 'fixed',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
minWidth: 200,
|
||||
maxWidth: 'fit-content',
|
||||
maxHeight: '85vh',
|
||||
padding: 20,
|
||||
marginTop: '-5vh',
|
||||
backgroundColor: 'white',
|
||||
borderRadius: 6,
|
||||
|
||||
'&:focus': {
|
||||
outline: 'none',
|
||||
},
|
||||
})
|
||||
|
|
|
@ -256,7 +256,7 @@ export const DropdownContent = styled(DropdownMenu.Content, {
|
|||
},
|
||||
})
|
||||
|
||||
export function DashSolidIcon() {
|
||||
export function DashSolidIcon(): JSX.Element {
|
||||
return (
|
||||
<svg width="24" height="24" stroke="currentColor">
|
||||
<circle
|
||||
|
@ -271,7 +271,7 @@ export function DashSolidIcon() {
|
|||
)
|
||||
}
|
||||
|
||||
export function DashDashedIcon() {
|
||||
export function DashDashedIcon(): JSX.Element {
|
||||
return (
|
||||
<svg width="24" height="24" stroke="currentColor">
|
||||
<circle
|
||||
|
@ -289,7 +289,7 @@ export function DashDashedIcon() {
|
|||
|
||||
const dottedDasharray = `${50.26548 * 0.025} ${50.26548 * 0.1}`
|
||||
|
||||
export function DashDottedIcon() {
|
||||
export function DashDottedIcon(): JSX.Element {
|
||||
return (
|
||||
<svg width="24" height="24" stroke="currentColor">
|
||||
<circle
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import { useStateDesigner } from '@state-designer/react'
|
||||
import state from 'state'
|
||||
import styled from 'styles'
|
||||
import { useRef } from 'react'
|
||||
|
||||
export default function StatusBar() {
|
||||
export default function StatusBar(): JSX.Element {
|
||||
const local = useStateDesigner(state)
|
||||
const { count, time } = useRenderCount()
|
||||
|
||||
const active = local.active.slice(1).map((s) => {
|
||||
const states = s.split('.')
|
||||
|
@ -22,11 +20,6 @@ export default function StatusBar() {
|
|||
<Section>
|
||||
{active.join(' | ')} | {log}
|
||||
</Section>
|
||||
{/* <Section
|
||||
title="Renders | Time"
|
||||
>
|
||||
{count} | {time.toString().padStart(3, '0')}
|
||||
</Section> */}
|
||||
</StatusBarContainer>
|
||||
)
|
||||
}
|
||||
|
@ -62,18 +55,3 @@ const Section = styled('div', {
|
|||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
})
|
||||
|
||||
function useRenderCount() {
|
||||
const rTime = useRef(Date.now())
|
||||
const rCounter = useRef(0)
|
||||
|
||||
rCounter.current++
|
||||
const now = Date.now()
|
||||
let time = now - rTime.current
|
||||
if (time > 100) {
|
||||
time = 0
|
||||
}
|
||||
rTime.current = now
|
||||
|
||||
return { count: rCounter.current, time }
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ export default function AlignDistribute({
|
|||
}: {
|
||||
hasTwoOrMore: boolean
|
||||
hasThreeOrMore: boolean
|
||||
}) {
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<Container>
|
||||
<IconButton
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { IconButton } from 'components/shared'
|
||||
import { strokes } from 'lib/shape-styles'
|
||||
import { strokes } from 'state/shape-styles'
|
||||
import { ColorStyle } from 'types'
|
||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||
import { Square } from 'react-feather'
|
||||
|
@ -9,7 +9,7 @@ export default function ColorContent({
|
|||
onChange,
|
||||
}: {
|
||||
onChange: (color: ColorStyle) => void
|
||||
}) {
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<DropdownContent sideOffset={8} side="bottom">
|
||||
{Object.keys(strokes).map((color: ColorStyle) => (
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||
import { strokes } from 'lib/shape-styles'
|
||||
import { strokes } from 'state/shape-styles'
|
||||
import { ColorStyle } from 'types'
|
||||
import { RowButton, IconWrapper } from '../shared'
|
||||
import { Square } from 'react-feather'
|
||||
|
@ -10,7 +10,7 @@ interface Props {
|
|||
onChange: (color: ColorStyle) => void
|
||||
}
|
||||
|
||||
export default function ColorPicker({ color, onChange }: Props) {
|
||||
export default function ColorPicker({ color, onChange }: Props): JSX.Element {
|
||||
return (
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger
|
||||
|
|
|
@ -17,7 +17,7 @@ interface Props {
|
|||
dash: DashStyle
|
||||
}
|
||||
|
||||
export default function DashPicker({ dash }: Props) {
|
||||
export default function DashPicker({ dash }: Props): JSX.Element {
|
||||
return (
|
||||
<Group name="Dash" onValueChange={handleChange}>
|
||||
<Item
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as Checkbox from '@radix-ui/react-checkbox'
|
||||
import { CheckIcon } from '@radix-ui/react-icons'
|
||||
import { strokes } from 'lib/shape-styles'
|
||||
import { strokes } from 'state/shape-styles'
|
||||
import { Square } from 'react-feather'
|
||||
import { IconWrapper, RowButton } from '../shared'
|
||||
|
||||
|
@ -9,7 +9,10 @@ interface Props {
|
|||
onChange: (isFilled: boolean | string) => void
|
||||
}
|
||||
|
||||
export default function IsFilledPicker({ isFilled, onChange }: Props) {
|
||||
export default function IsFilledPicker({
|
||||
isFilled,
|
||||
onChange,
|
||||
}: Props): JSX.Element {
|
||||
return (
|
||||
<Checkbox.Root
|
||||
as={RowButton}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||
import { IconButton } from 'components/shared'
|
||||
import Tooltip from 'components/tooltip'
|
||||
import { strokes } from 'lib/shape-styles'
|
||||
import { strokes } from 'state/shape-styles'
|
||||
import { Square } from 'react-feather'
|
||||
import state, { useSelector } from 'state'
|
||||
import ColorContent from './color-content'
|
||||
|
||||
export default function QuickColorSelect() {
|
||||
export default function QuickColorSelect(): JSX.Element {
|
||||
const color = useSelector((s) => s.values.selectedStyle.color)
|
||||
|
||||
return (
|
||||
|
|
|
@ -17,7 +17,7 @@ const dashes = {
|
|||
[DashStyle.Dotted]: <DashDottedIcon />,
|
||||
}
|
||||
|
||||
export default function QuickdashSelect() {
|
||||
export default function QuickdashSelect(): JSX.Element {
|
||||
const dash = useSelector((s) => s.values.selectedStyle.dash)
|
||||
|
||||
return (
|
||||
|
|
|
@ -12,7 +12,7 @@ const sizes = {
|
|||
[SizeStyle.Large]: 22,
|
||||
}
|
||||
|
||||
export default function QuickSizeSelect() {
|
||||
export default function QuickSizeSelect(): JSX.Element {
|
||||
const size = useSelector((s) => s.values.selectedStyle.size)
|
||||
|
||||
return (
|
||||
|
|
|
@ -8,7 +8,7 @@ function handleChange(size: string) {
|
|||
state.send('CHANGED_STYLE', { size })
|
||||
}
|
||||
|
||||
export default function SizePicker({ size }: { size: SizeStyle }) {
|
||||
export default function SizePicker({ size }: { size: SizeStyle }): JSX.Element {
|
||||
return (
|
||||
<Group name="width" onValueChange={handleChange}>
|
||||
<Item
|
||||
|
|
|
@ -36,7 +36,7 @@ import QuickSizeSelect from './quick-size-select'
|
|||
import QuickdashSelect from './quick-dash-select'
|
||||
import Tooltip from 'components/tooltip'
|
||||
|
||||
export default function StylePanel() {
|
||||
export default function StylePanel(): JSX.Element {
|
||||
const rContainer = useRef<HTMLDivElement>(null)
|
||||
const isOpen = useSelector((s) => s.data.settings.isStyleOpen)
|
||||
|
||||
|
@ -69,7 +69,7 @@ export default function StylePanel() {
|
|||
// information, based on the user's current selection. We might have to keep
|
||||
// track of this data manually within our state.
|
||||
|
||||
function SelectedShapeStyles() {
|
||||
function SelectedShapeStyles(): JSX.Element {
|
||||
const selectedIds = useSelector(
|
||||
(s) => setToArray(getSelectedIds(s.data)),
|
||||
deepCompareArrays
|
||||
|
|
|
@ -2,13 +2,10 @@ import {
|
|||
ArrowTopRightIcon,
|
||||
CircleIcon,
|
||||
CursorArrowIcon,
|
||||
DividerHorizontalIcon,
|
||||
DotIcon,
|
||||
LockClosedIcon,
|
||||
LockOpen1Icon,
|
||||
Pencil1Icon,
|
||||
Pencil2Icon,
|
||||
SewingPinIcon,
|
||||
SquareIcon,
|
||||
TextIcon,
|
||||
} from '@radix-ui/react-icons'
|
||||
|
@ -22,19 +19,14 @@ import Zoom from './zoom'
|
|||
import Tooltip from '../tooltip'
|
||||
|
||||
const selectArrowTool = () => state.send('SELECTED_ARROW_TOOL')
|
||||
const selectCircleTool = () => state.send('SELECTED_CIRCLE_TOOL')
|
||||
const selectDotTool = () => state.send('SELECTED_DOT_TOOL')
|
||||
const selectDrawTool = () => state.send('SELECTED_DRAW_TOOL')
|
||||
const selectEllipseTool = () => state.send('SELECTED_ELLIPSE_TOOL')
|
||||
const selectLineTool = () => state.send('SELECTED_LINE_TOOL')
|
||||
const selectPolylineTool = () => state.send('SELECTED_POLYLINE_TOOL')
|
||||
const selectTextTool = () => state.send('SELECTED_TEXT_TOOL')
|
||||
const selectRayTool = () => state.send('SELECTED_RAY_TOOL')
|
||||
const selectRectangleTool = () => state.send('SELECTED_RECTANGLE_TOOL')
|
||||
const selectSelectTool = () => state.send('SELECTED_SELECT_TOOL')
|
||||
const selectToolLock = () => state.send('TOGGLED_TOOL_LOCK')
|
||||
|
||||
export default function ToolsPanel() {
|
||||
export default function ToolsPanel(): JSX.Element {
|
||||
const activeTool = useSelector((s) => s.data.activeTool)
|
||||
|
||||
const isToolLocked = useSelector((s) => s.data.settings.isToolLocked)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { IconButton } from 'components/shared'
|
||||
import { RotateCcw, RotateCw, Trash2 } from 'react-feather'
|
||||
import state, { useSelector } from 'state'
|
||||
import state from 'state'
|
||||
import styled from 'styles'
|
||||
import Tooltip from '../tooltip'
|
||||
|
||||
|
@ -8,7 +8,7 @@ const undo = () => state.send('UNDO')
|
|||
const redo = () => state.send('REDO')
|
||||
const clear = () => state.send('CLEARED_PAGE')
|
||||
|
||||
export default function UndoRedo() {
|
||||
export default function UndoRedo(): JSX.Element {
|
||||
return (
|
||||
<Container size={{ '@sm': 'small' }}>
|
||||
<Tooltip label="Undo">
|
||||
|
|
|
@ -10,7 +10,7 @@ const zoomOut = () => state.send('ZOOMED_OUT')
|
|||
const zoomToFit = () => state.send('ZOOMED_TO_FIT')
|
||||
const zoomToActual = () => state.send('ZOOMED_TO_ACTUAL')
|
||||
|
||||
export default function Zoom() {
|
||||
export default function Zoom(): JSX.Element {
|
||||
return (
|
||||
<Container size={{ '@sm': 'small' }}>
|
||||
<Tooltip label="Zoom Out">
|
||||
|
|
|
@ -10,7 +10,7 @@ export default function Tooltip({
|
|||
children: React.ReactNode
|
||||
label: string
|
||||
side?: 'bottom' | 'left' | 'right' | 'top'
|
||||
}) {
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<_Tooltip.Root>
|
||||
<_Tooltip.Trigger as="span">{children}</_Tooltip.Trigger>
|
||||
|
|
2
decs.d.ts
vendored
2
decs.d.ts
vendored
|
@ -20,7 +20,7 @@ interface FontFace {
|
|||
interface FontFaceSet {
|
||||
readonly status: FontFaceSetStatus
|
||||
readonly ready: Promise<FontFaceSet>
|
||||
check(font: string, text?: string): Boolean
|
||||
check(font: string, text?: string): boolean
|
||||
load(font: string, text?: string): Promise<FontFace[]>
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { useCallback } from 'react'
|
||||
import { fastTransform } from 'state/hacks'
|
||||
import inputs from 'state/inputs'
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import React, { useEffect } from 'react'
|
||||
import state from 'state'
|
||||
import storage from 'state/storage'
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { MutableRefObject, useCallback } from 'react'
|
||||
import state from 'state'
|
||||
import {
|
||||
|
@ -7,7 +8,6 @@ import {
|
|||
fastTranslate,
|
||||
} from 'state/hacks'
|
||||
import inputs from 'state/inputs'
|
||||
import { isMobile } from 'utils/utils'
|
||||
|
||||
export default function useCanvasEvents(
|
||||
rCanvas: MutableRefObject<SVGGElement>
|
||||
|
@ -58,7 +58,7 @@ export default function useCanvasEvents(
|
|||
state.send('STOPPED_POINTING', { id: 'canvas', ...inputs.pointerUp(e) })
|
||||
}, [])
|
||||
|
||||
const handleTouchStart = useCallback((e: React.TouchEvent) => {
|
||||
const handleTouchStart = useCallback(() => {
|
||||
// if (isMobile()) {
|
||||
// if (e.touches.length === 2) {
|
||||
// state.send('TOUCH_UNDO')
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import router from 'next/router'
|
||||
import { useEffect } from 'react'
|
||||
import * as gtag from 'utils/gtag'
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { MutableRefObject, useCallback } from 'react'
|
||||
import state from 'state'
|
||||
import inputs from 'state/inputs'
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { useEffect } from 'react'
|
||||
import state from 'state'
|
||||
import { MoveType } from 'types'
|
||||
|
@ -157,6 +158,7 @@ export default function useKeyboardEvents() {
|
|||
}
|
||||
case 'o': {
|
||||
if (metaKey(e)) {
|
||||
break
|
||||
} else {
|
||||
state.send('SELECTED_DOT_TOOL', getKeyboardEventInfo(e))
|
||||
}
|
||||
|
@ -204,6 +206,7 @@ export default function useKeyboardEvents() {
|
|||
}
|
||||
case 'i': {
|
||||
if (metaKey(e)) {
|
||||
break
|
||||
} else {
|
||||
state.send('SELECTED_CIRCLE_TOOL', getKeyboardEventInfo(e))
|
||||
}
|
||||
|
@ -221,6 +224,7 @@ export default function useKeyboardEvents() {
|
|||
}
|
||||
case 'y': {
|
||||
if (metaKey(e)) {
|
||||
break
|
||||
} else {
|
||||
state.send('SELECTED_RAY_TOOL', getKeyboardEventInfo(e))
|
||||
}
|
||||
|
@ -228,6 +232,7 @@ export default function useKeyboardEvents() {
|
|||
}
|
||||
case 'p': {
|
||||
if (metaKey(e)) {
|
||||
break
|
||||
} else {
|
||||
state.send('SELECTED_POLYLINE_TOOL', getKeyboardEventInfo(e))
|
||||
}
|
||||
|
@ -235,6 +240,7 @@ export default function useKeyboardEvents() {
|
|||
}
|
||||
case 'r': {
|
||||
if (metaKey(e)) {
|
||||
break
|
||||
} else {
|
||||
state.send('SELECTED_RECTANGLE_TOOL', getKeyboardEventInfo(e))
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { useEffect } from 'react'
|
||||
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 inputs from 'state/inputs'
|
||||
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import { useCallback } from "react"
|
||||
import state, { useSelector } from "state"
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { useCallback } from 'react'
|
||||
import state, { useSelector } from 'state'
|
||||
|
||||
export default function useTheme() {
|
||||
const theme = useSelector((state) =>
|
||||
state.data.settings.isDarkMode ? "dark" : "light"
|
||||
state.data.settings.isDarkMode ? 'dark' : 'light'
|
||||
)
|
||||
|
||||
const toggleTheme = useCallback(() => state.send("TOGGLED_THEME"), [])
|
||||
const toggleTheme = useCallback(() => state.send('TOGGLED_THEME'), [])
|
||||
|
||||
return { theme, toggleTheme }
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { useRef } from 'react'
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import { useRef } from 'react'
|
||||
|
||||
import state from 'state'
|
||||
import inputs from 'state/inputs'
|
||||
import vec from 'utils/vec'
|
||||
|
@ -47,13 +48,12 @@ export default function useZoomEvents() {
|
|||
rPinchPoint.current = origin
|
||||
}
|
||||
|
||||
const [distanceDelta, angleDelta] = vec.sub(rPinchDa.current, da)
|
||||
const [distanceDelta] = vec.sub(rPinchDa.current, da)
|
||||
|
||||
fastPinchCamera(
|
||||
origin,
|
||||
vec.sub(rPinchPoint.current, origin),
|
||||
distanceDelta,
|
||||
angleDelta
|
||||
distanceDelta
|
||||
)
|
||||
|
||||
rPinchDa.current = da
|
||||
|
|
19
jest.config.js
Normal file
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",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"name": "with-typescript-eslint-jest",
|
||||
"author": "@erikdstock",
|
||||
"license": "MIT",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"test": "yarn test:app",
|
||||
"test:all": "yarn test:code",
|
||||
"test:update": "yarn test:app --updateSnapshot --watchAll=false"
|
||||
"type-check": "tsc --pretty --noEmit",
|
||||
"format": "prettier --write .",
|
||||
"lint": "eslint . --ext ts --ext tsx --ext js",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watchAll",
|
||||
"test:update": "jest --updateSnapshot",
|
||||
"test-all": "yarn lint && yarn type-check && yarn test"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged",
|
||||
"pre-push": "yarn run type-check"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.@(ts|tsx)": [
|
||||
"yarn lint",
|
||||
"yarn format"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@monaco-editor/react": "^4.1.3",
|
||||
"@monaco-editor/react": "^4.2.1",
|
||||
"@radix-ui/react-checkbox": "^0.0.16",
|
||||
"@radix-ui/react-context-menu": "^0.0.21",
|
||||
"@radix-ui/react-context-menu": "^0.0.22",
|
||||
"@radix-ui/react-dialog": "^0.0.18",
|
||||
"@radix-ui/react-dropdown-menu": "^0.0.20",
|
||||
"@radix-ui/react-dropdown-menu": "^0.0.21",
|
||||
"@radix-ui/react-hover-card": "^0.0.3",
|
||||
"@radix-ui/react-icons": "^1.0.3",
|
||||
"@radix-ui/react-radio-group": "^0.0.17",
|
||||
"@radix-ui/react-tooltip": "^0.0.19",
|
||||
"@sentry/integrations": "^6.7.1",
|
||||
"@sentry/nextjs": "^6.7.1",
|
||||
"@sentry/node": "^6.7.1",
|
||||
"@sentry/react": "^6.7.1",
|
||||
"@sentry/tracing": "^6.7.1",
|
||||
"@sentry/integrations": "^6.7.2",
|
||||
"@sentry/nextjs": "^6.7.2",
|
||||
"@sentry/node": "^6.7.2",
|
||||
"@sentry/react": "^6.7.2",
|
||||
"@sentry/tracing": "^6.7.2",
|
||||
"@sentry/webpack-plugin": "^1.15.1",
|
||||
"@state-designer/react": "^1.7.3",
|
||||
"@stitches/react": "^0.2.1",
|
||||
"@state-designer/react": "^1.7.32",
|
||||
"@stitches/react": "^0.2.2",
|
||||
"@types/uuid": "^8.3.0",
|
||||
"browser-fs-access": "^0.17.3",
|
||||
"framer-motion": "^4.1.16",
|
||||
"framer-motion": "^4.1.17",
|
||||
"gtag": "^1.0.1",
|
||||
"idb-keyval": "^5.0.6",
|
||||
"ismobilejs": "^1.1.1",
|
||||
"next": "10.2.0",
|
||||
"monaco-editor": "^0.25.2",
|
||||
"next": "latest",
|
||||
"next-auth": "^3.27.0",
|
||||
"next-pwa": "^5.2.21",
|
||||
"perfect-freehand": "^0.4.9",
|
||||
"prettier": "^2.3.0",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-feather": "^2.0.9",
|
||||
"react-use-gesture": "^9.1.3",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/next": "^9.0.0",
|
||||
"@types/react": "^17.0.5",
|
||||
"@types/react-dom": "^17.0.3",
|
||||
"@types/uuid": "^8.3.0",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
"@types/jest": "^26.0.23",
|
||||
"@types/node": "^14.14.25",
|
||||
"@types/react": "^17.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^4.14.2",
|
||||
"@typescript-eslint/parser": "^4.14.2",
|
||||
"babel-jest": "^27.0.2",
|
||||
"eslint": "^7.19.0",
|
||||
"eslint-config-next": "^11.0.0",
|
||||
"eslint-config-prettier": "^7.2.0",
|
||||
"eslint-plugin-react": "^7.19.0",
|
||||
"husky": "^4.2.3",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest": "^27.0.4",
|
||||
"monaco-editor": "^0.24.0",
|
||||
"typescript": "^4.2.4"
|
||||
"jest-watch-typeahead": "^0.6.1",
|
||||
"lint-staged": "^10.0.10",
|
||||
"prettier": "^2.3.1",
|
||||
"typescript": "^4.1.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import useGtag from 'hooks/useGtag'
|
||||
import { AppProps } from 'next/app'
|
||||
import { globalStyles } from 'styles'
|
||||
import 'styles/globals.css'
|
||||
import { Provider } from 'next-auth/client'
|
||||
import 'styles/globals.css'
|
||||
|
||||
function MyApp({ Component, pageProps }: AppProps) {
|
||||
function MyApp({ Component, pageProps }: AppProps): JSX.Element {
|
||||
globalStyles()
|
||||
|
||||
useGtag()
|
||||
|
|
|
@ -1,9 +1,19 @@
|
|||
import NextDocument, { Html, Head, Main, NextScript } from 'next/document'
|
||||
import NextDocument, {
|
||||
Html,
|
||||
Head,
|
||||
Main,
|
||||
NextScript,
|
||||
DocumentContext,
|
||||
} from 'next/document'
|
||||
import { dark, getCssString } from 'styles'
|
||||
import { GA_TRACKING_ID } from 'utils/gtag'
|
||||
|
||||
class MyDocument extends NextDocument {
|
||||
static async getInitialProps(ctx) {
|
||||
static async getInitialProps(ctx: DocumentContext): Promise<{
|
||||
styles: JSX.Element
|
||||
html: string
|
||||
head?: JSX.Element[]
|
||||
}> {
|
||||
try {
|
||||
const initialProps = await NextDocument.getInitialProps(ctx)
|
||||
|
||||
|
@ -22,10 +32,11 @@ class MyDocument extends NextDocument {
|
|||
} catch (e) {
|
||||
console.error(e.message)
|
||||
} finally {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
render(): JSX.Element {
|
||||
return (
|
||||
<Html lang="en">
|
||||
<Head>
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { NextApiHandler, NextApiRequest, NextApiResponse } from 'next'
|
||||
import NextAuth from 'next-auth'
|
||||
import Providers from 'next-auth/providers'
|
||||
|
||||
export default function (req: NextApiRequest, res: NextApiResponse) {
|
||||
export default function Auth(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
): ReturnType<NextApiHandler> {
|
||||
return NextAuth(req, res, {
|
||||
providers: [
|
||||
Providers.GitHub({
|
||||
|
@ -15,8 +18,7 @@ export default function (req: NextApiRequest, res: NextApiResponse) {
|
|||
async redirect(url, baseUrl) {
|
||||
return url.startsWith(baseUrl) ? url : baseUrl
|
||||
},
|
||||
async signIn(user, account, profile) {
|
||||
// @ts-ignore
|
||||
async signIn(user, account, profile: any) {
|
||||
const canLogin = await isSponsoringMe(profile?.login)
|
||||
|
||||
if (canLogin) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export default function CreateError() {
|
||||
export default function CreateError(): JSX.Element {
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
// import Editor from "components/editor"
|
||||
import Head from 'next/head'
|
||||
import dynamic from 'next/dynamic'
|
||||
import { GetServerSideProps } from 'next'
|
||||
|
@ -6,7 +5,7 @@ import { getSession } from 'next-auth/client'
|
|||
|
||||
const Editor = dynamic(() => import('components/editor'), { ssr: false })
|
||||
|
||||
export default function Home() {
|
||||
export default function Home(): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
|
|
|
@ -4,7 +4,7 @@ import dynamic from 'next/dynamic'
|
|||
|
||||
const Editor = dynamic(() => import('components/editor'), { ssr: false })
|
||||
|
||||
export default function Home() {
|
||||
export default function Home(): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { GetServerSideProps, NextApiRequest, NextApiResponse } from 'next'
|
||||
import { signOut, signout } from 'next-auth/client'
|
||||
import { signOut } from 'next-auth/client'
|
||||
|
||||
export default function SignOut() {
|
||||
export default function SignOut(): JSX.Element {
|
||||
return (
|
||||
<div>
|
||||
<button onClick={() => signOut()}>Sign Out</button>
|
||||
|
|
|
@ -3,7 +3,7 @@ import { getSession, signin, signout, useSession } from 'next-auth/client'
|
|||
import { GetServerSideProps } from 'next'
|
||||
import React from 'react'
|
||||
|
||||
export default function Sponsorware() {
|
||||
export default function Sponsorware(): JSX.Element {
|
||||
const [session, loading] = useSession()
|
||||
|
||||
return (
|
||||
|
@ -27,7 +27,7 @@ export default function Sponsorware() {
|
|||
<li>only available for my sponsors</li>
|
||||
</ul>
|
||||
<p>
|
||||
If you'd like to try it out,{' '}
|
||||
If you'd like to try it out,{' '}
|
||||
<a
|
||||
href="https://github.com/sponsors/steveruizok"
|
||||
target="_blank"
|
||||
|
@ -45,7 +45,7 @@ export default function Sponsorware() {
|
|||
</Button>
|
||||
<Detail>
|
||||
Signed in as {session?.user?.name} ({session?.user?.email}), but
|
||||
it looks like you're not yet a sponsor.
|
||||
it looks like you're not yet a sponsor.
|
||||
<br />
|
||||
Something wrong? Try <a href="/">reloading the page</a> or DM me
|
||||
on <a href="https://twitter.com/steveruizok">Twitter</a>.
|
||||
|
@ -67,7 +67,7 @@ export default function Sponsorware() {
|
|||
export const getServerSideProps: GetServerSideProps = async (context) => {
|
||||
const session = await getSession(context)
|
||||
|
||||
if (!!session?.user) {
|
||||
if (session?.user) {
|
||||
context.res.setHeader('Location', `/`)
|
||||
context.res.statusCode = 307
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
1
public/sw.js.map
Normal file
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 { getCommonBounds, getSelectedIds, getSelectedShapes } from 'utils/utils'
|
||||
import { getCommonBounds, getSelectedShapes } from 'utils/utils'
|
||||
import state from './state'
|
||||
|
||||
class Clipboard {
|
||||
|
@ -99,10 +99,9 @@ class Clipboard {
|
|||
}
|
||||
|
||||
static copyStringToClipboard(string: string) {
|
||||
let textarea: HTMLTextAreaElement
|
||||
let result: boolean | null
|
||||
|
||||
textarea = document.createElement('textarea')
|
||||
const textarea = document.createElement('textarea')
|
||||
textarea.setAttribute('position', 'fixed')
|
||||
textarea.setAttribute('top', '0')
|
||||
textarea.setAttribute('readonly', 'true')
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import CodeShape from './index'
|
||||
import { uniqueId } from 'utils/utils'
|
||||
import { CircleShape, ShapeType } from 'types'
|
||||
import { vectorToPoint } from 'utils/utils'
|
||||
import { defaultStyle } from 'lib/shape-styles'
|
||||
import Utils from './utils'
|
||||
import { defaultStyle } from 'state/shape-styles'
|
||||
|
||||
export default class Circle extends CodeShape<CircleShape> {
|
||||
constructor(props = {} as Partial<CircleShape>) {
|
||||
props.point = vectorToPoint(props.point)
|
||||
props.point = Utils.vectorToPoint(props.point)
|
||||
|
||||
super({
|
||||
id: uniqueId(),
|
||||
|
@ -27,15 +27,15 @@ export default class Circle extends CodeShape<CircleShape> {
|
|||
})
|
||||
}
|
||||
|
||||
export() {
|
||||
export(): CircleShape {
|
||||
const shape = { ...this.shape }
|
||||
|
||||
shape.point = vectorToPoint(shape.point)
|
||||
shape.point = Utils.vectorToPoint(shape.point)
|
||||
|
||||
return shape
|
||||
}
|
||||
|
||||
get radius() {
|
||||
get radius(): number {
|
||||
return this.shape.radius
|
||||
}
|
||||
}
|
|
@ -25,7 +25,7 @@ export class Control<T extends CodeControl> {
|
|||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
destroy(): void {
|
||||
codeControls.delete(this.control)
|
||||
delete controls[this.control.label]
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
import CodeShape from './index'
|
||||
import { uniqueId } from 'utils/utils'
|
||||
import { DotShape, ShapeType } from 'types'
|
||||
import { vectorToPoint } from 'utils/utils'
|
||||
import { defaultStyle } from 'lib/shape-styles'
|
||||
import { defaultStyle } from 'state/shape-styles'
|
||||
import Utils from './utils'
|
||||
|
||||
export default class Dot extends CodeShape<DotShape> {
|
||||
constructor(props = {} as Partial<DotShape>) {
|
||||
props.point = vectorToPoint(props.point)
|
||||
props.point = Utils.vectorToPoint(props.point)
|
||||
|
||||
super({
|
||||
id: uniqueId(),
|
||||
|
@ -30,10 +30,10 @@ export default class Dot extends CodeShape<DotShape> {
|
|||
})
|
||||
}
|
||||
|
||||
export() {
|
||||
export(): DotShape {
|
||||
const shape = { ...this.shape }
|
||||
|
||||
shape.point = vectorToPoint(shape.point)
|
||||
shape.point = Utils.vectorToPoint(shape.point)
|
||||
|
||||
return shape
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
import CodeShape from './index'
|
||||
import { uniqueId } from 'utils/utils'
|
||||
import { EllipseShape, ShapeType } from 'types'
|
||||
import { vectorToPoint } from 'utils/utils'
|
||||
import { defaultStyle } from 'lib/shape-styles'
|
||||
import Utils from './utils'
|
||||
import { defaultStyle } from 'state/shape-styles'
|
||||
|
||||
export default class Ellipse extends CodeShape<EllipseShape> {
|
||||
constructor(props = {} as Partial<EllipseShape>) {
|
||||
props.point = vectorToPoint(props.point)
|
||||
props.point = Utils.vectorToPoint(props.point)
|
||||
|
||||
super({
|
||||
id: uniqueId(),
|
||||
|
@ -28,19 +28,19 @@ export default class Ellipse extends CodeShape<EllipseShape> {
|
|||
})
|
||||
}
|
||||
|
||||
export() {
|
||||
export(): EllipseShape {
|
||||
const shape = { ...this.shape }
|
||||
|
||||
shape.point = vectorToPoint(shape.point)
|
||||
shape.point = Utils.vectorToPoint(shape.point)
|
||||
|
||||
return shape
|
||||
}
|
||||
|
||||
get radiusX() {
|
||||
get radiusX(): number {
|
||||
return this.shape.radiusX
|
||||
}
|
||||
|
||||
get radiusY() {
|
||||
get radiusY(): number {
|
||||
return this.shape.radiusY
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@ import Vector from './vector'
|
|||
import Utils from './utils'
|
||||
import { NumberControl, VectorControl, codeControls, controls } from './control'
|
||||
import { codeShapes } from './index'
|
||||
import { CodeControl, Data } from 'types'
|
||||
import { CodeControl, Data, Shape } from 'types'
|
||||
|
||||
const baseScope = {
|
||||
Dot,
|
||||
|
@ -30,7 +30,13 @@ const baseScope = {
|
|||
* collected shapes as an array.
|
||||
* @param code
|
||||
*/
|
||||
export function generateFromCode(data: Data, code: string) {
|
||||
export function generateFromCode(
|
||||
data: Data,
|
||||
code: string
|
||||
): {
|
||||
shapes: Shape[]
|
||||
controls: CodeControl[]
|
||||
} {
|
||||
codeControls.clear()
|
||||
codeShapes.clear()
|
||||
;(window as any).isUpdatingCode = false
|
||||
|
@ -55,7 +61,12 @@ export function generateFromCode(data: Data, code: string) {
|
|||
* collected shapes as an array.
|
||||
* @param code
|
||||
*/
|
||||
export function updateFromCode(data: Data, code: string) {
|
||||
export function updateFromCode(
|
||||
data: Data,
|
||||
code: string
|
||||
): {
|
||||
shapes: Shape[]
|
||||
} {
|
||||
codeShapes.clear()
|
||||
;(window as any).isUpdatingCode = true
|
||||
;(window as any).currentPageId = data.currentPageId
|
||||
|
@ -66,7 +77,7 @@ export function updateFromCode(data: Data, code: string) {
|
|||
...baseScope,
|
||||
currentPageId,
|
||||
controls: Object.fromEntries(
|
||||
Object.entries(controls).map(([id, control]) => [
|
||||
Object.entries(controls).map(([_, control]) => [
|
||||
control.label,
|
||||
control.value,
|
||||
])
|
|
@ -1,12 +1,8 @@
|
|||
import { Mutable, Shape } from 'types'
|
||||
import shapeUtilityMap, {
|
||||
createShape,
|
||||
getShapeUtils,
|
||||
ShapeUtility,
|
||||
} from 'lib/shape-utils'
|
||||
import { Mutable, Shape, ShapeUtility } from 'types'
|
||||
import { createShape, getShapeUtils } from 'state/shape-utils'
|
||||
import vec from 'utils/vec'
|
||||
import Vector from './vector'
|
||||
import { vectorToPoint } from 'utils/utils'
|
||||
import Utils from './utils'
|
||||
|
||||
export const codeShapes = new Set<CodeShape<Shape>>([])
|
||||
|
||||
|
@ -24,48 +20,48 @@ export default class CodeShape<T extends Shape> {
|
|||
codeShapes.add(this)
|
||||
}
|
||||
|
||||
destroy() {
|
||||
destroy(): void {
|
||||
codeShapes.delete(this)
|
||||
}
|
||||
|
||||
moveTo(point: Vector) {
|
||||
this.utils.setProperty(this._shape, 'point', vectorToPoint(point))
|
||||
moveTo(point: Vector): CodeShape<T> {
|
||||
this.utils.setProperty(this._shape, 'point', Utils.vectorToPoint(point))
|
||||
return this
|
||||
}
|
||||
|
||||
translate(delta: Vector) {
|
||||
translate(delta: Vector): CodeShape<T> {
|
||||
this.utils.setProperty(
|
||||
this._shape,
|
||||
'point',
|
||||
vec.add(this._shape.point, vectorToPoint(delta))
|
||||
vec.add(this._shape.point, Utils.vectorToPoint(delta))
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
rotate(rotation: number) {
|
||||
rotate(rotation: number): CodeShape<T> {
|
||||
this.utils.setProperty(this._shape, 'rotation', rotation)
|
||||
return this
|
||||
}
|
||||
|
||||
getBounds() {
|
||||
getBounds(): CodeShape<T> {
|
||||
this.utils.getBounds(this.shape)
|
||||
return this
|
||||
}
|
||||
|
||||
hitTest(point: Vector) {
|
||||
this.utils.hitTest(this.shape, vectorToPoint(point))
|
||||
hitTest(point: Vector): CodeShape<T> {
|
||||
this.utils.hitTest(this.shape, Utils.vectorToPoint(point))
|
||||
return this
|
||||
}
|
||||
|
||||
get shape() {
|
||||
get shape(): T {
|
||||
return this._shape
|
||||
}
|
||||
|
||||
get point() {
|
||||
get point(): number[] {
|
||||
return [...this.shape.point]
|
||||
}
|
||||
|
||||
get rotation() {
|
||||
get rotation(): number {
|
||||
return this.shape.rotation
|
||||
}
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
import CodeShape from './index'
|
||||
import { uniqueId } from 'utils/utils'
|
||||
import { LineShape, ShapeType } from 'types'
|
||||
import { vectorToPoint } from 'utils/utils'
|
||||
import { defaultStyle } from 'lib/shape-styles'
|
||||
import { defaultStyle } from 'state/shape-styles'
|
||||
import Utils from './utils'
|
||||
|
||||
export default class Line extends CodeShape<LineShape> {
|
||||
constructor(props = {} as Partial<LineShape>) {
|
||||
props.point = vectorToPoint(props.point)
|
||||
props.direction = vectorToPoint(props.direction)
|
||||
props.point = Utils.vectorToPoint(props.point)
|
||||
props.direction = Utils.vectorToPoint(props.direction)
|
||||
|
||||
super({
|
||||
id: uniqueId(),
|
||||
|
@ -32,16 +32,16 @@ export default class Line extends CodeShape<LineShape> {
|
|||
})
|
||||
}
|
||||
|
||||
export() {
|
||||
export(): LineShape {
|
||||
const shape = { ...this.shape }
|
||||
|
||||
shape.point = vectorToPoint(shape.point)
|
||||
shape.direction = vectorToPoint(shape.direction)
|
||||
shape.point = Utils.vectorToPoint(shape.point)
|
||||
shape.direction = Utils.vectorToPoint(shape.direction)
|
||||
|
||||
return shape
|
||||
}
|
||||
|
||||
get direction() {
|
||||
get direction(): number[] {
|
||||
return this.shape.direction
|
||||
}
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
import CodeShape from './index'
|
||||
import { uniqueId } from 'utils/utils'
|
||||
import { PolylineShape, ShapeType } from 'types'
|
||||
import { vectorToPoint } from 'utils/utils'
|
||||
import { defaultStyle } from 'lib/shape-styles'
|
||||
import { defaultStyle } from 'state/shape-styles'
|
||||
import Utils from './utils'
|
||||
|
||||
export default class Polyline extends CodeShape<PolylineShape> {
|
||||
constructor(props = {} as Partial<PolylineShape>) {
|
||||
props.point = vectorToPoint(props.point)
|
||||
props.points = props.points.map(vectorToPoint)
|
||||
props.point = Utils.vectorToPoint(props.point)
|
||||
props.points = props.points.map(Utils.vectorToPoint)
|
||||
|
||||
super({
|
||||
id: uniqueId(),
|
||||
|
@ -28,16 +28,16 @@ export default class Polyline extends CodeShape<PolylineShape> {
|
|||
})
|
||||
}
|
||||
|
||||
export() {
|
||||
export(): PolylineShape {
|
||||
const shape = { ...this.shape }
|
||||
|
||||
shape.point = vectorToPoint(shape.point)
|
||||
shape.points = shape.points.map(vectorToPoint)
|
||||
shape.point = Utils.vectorToPoint(shape.point)
|
||||
shape.points = shape.points.map(Utils.vectorToPoint)
|
||||
|
||||
return shape
|
||||
}
|
||||
|
||||
get points() {
|
||||
get points(): number[][] {
|
||||
return this.shape.points
|
||||
}
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
import CodeShape from './index'
|
||||
import { uniqueId } from 'utils/utils'
|
||||
import { RayShape, ShapeType } from 'types'
|
||||
import { vectorToPoint } from 'utils/utils'
|
||||
import { defaultStyle } from 'lib/shape-styles'
|
||||
import { defaultStyle } from 'state/shape-styles'
|
||||
import Utils from './utils'
|
||||
|
||||
export default class Ray extends CodeShape<RayShape> {
|
||||
constructor(props = {} as Partial<RayShape>) {
|
||||
props.point = vectorToPoint(props.point)
|
||||
props.direction = vectorToPoint(props.direction)
|
||||
props.point = Utils.vectorToPoint(props.point)
|
||||
props.direction = Utils.vectorToPoint(props.direction)
|
||||
|
||||
super({
|
||||
id: uniqueId(),
|
||||
|
@ -32,16 +32,16 @@ export default class Ray extends CodeShape<RayShape> {
|
|||
})
|
||||
}
|
||||
|
||||
export() {
|
||||
export(): RayShape {
|
||||
const shape = { ...this.shape }
|
||||
|
||||
shape.point = vectorToPoint(shape.point)
|
||||
shape.direction = vectorToPoint(shape.direction)
|
||||
shape.point = Utils.vectorToPoint(shape.point)
|
||||
shape.direction = Utils.vectorToPoint(shape.direction)
|
||||
|
||||
return shape
|
||||
}
|
||||
|
||||
get direction() {
|
||||
get direction(): number[] {
|
||||
return this.shape.direction
|
||||
}
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
import CodeShape from './index'
|
||||
import { uniqueId } from 'utils/utils'
|
||||
import { RectangleShape, ShapeType } from 'types'
|
||||
import { vectorToPoint } from 'utils/utils'
|
||||
import { defaultStyle } from 'lib/shape-styles'
|
||||
import Utils from './utils'
|
||||
import { defaultStyle } from 'state/shape-styles'
|
||||
|
||||
export default class Rectangle extends CodeShape<RectangleShape> {
|
||||
constructor(props = {} as Partial<RectangleShape>) {
|
||||
props.point = vectorToPoint(props.point)
|
||||
props.size = vectorToPoint(props.size)
|
||||
props.point = Utils.vectorToPoint(props.point)
|
||||
props.size = Utils.vectorToPoint(props.size)
|
||||
|
||||
super({
|
||||
id: uniqueId(),
|
||||
|
@ -29,7 +29,7 @@ export default class Rectangle extends CodeShape<RectangleShape> {
|
|||
})
|
||||
}
|
||||
|
||||
get size() {
|
||||
get size(): number[] {
|
||||
return this.shape.size
|
||||
}
|
||||
}
|
|
@ -2,7 +2,23 @@ import { Bounds } from 'types'
|
|||
import Vector, { Point } from './vector'
|
||||
|
||||
export default class Utils {
|
||||
static getRayRayIntersection(p0: Vector, n0: Vector, p1: Vector, n1: Vector) {
|
||||
static vectorToPoint(point: number[] | Vector | undefined): number[] {
|
||||
if (typeof point === 'undefined') {
|
||||
return [0, 0]
|
||||
}
|
||||
|
||||
if (point instanceof Vector) {
|
||||
return [point.x, point.y]
|
||||
}
|
||||
return point
|
||||
}
|
||||
|
||||
static getRayRayIntersection(
|
||||
p0: Vector,
|
||||
n0: Vector,
|
||||
p1: Vector,
|
||||
n1: Vector
|
||||
): Vector {
|
||||
const p0e = Vector.add(p0, n0),
|
||||
p1e = Vector.add(p1, n1),
|
||||
m0 = (p0e.y - p0.y) / (p0e.x - p0.x),
|
||||
|
@ -20,7 +36,7 @@ export default class Utils {
|
|||
r0: number,
|
||||
P: Point | Vector,
|
||||
side: number
|
||||
) {
|
||||
): Vector {
|
||||
const v0 = Vector.cast(A)
|
||||
const v1 = Vector.cast(P)
|
||||
const B = Vector.lrp(v0, v1, 0.5),
|
||||
|
@ -41,17 +57,17 @@ export default class Utils {
|
|||
return side === 0 ? p.add(k) : p.sub(k)
|
||||
}
|
||||
|
||||
static shortAngleDist(a: number, b: number) {
|
||||
static shortAngleDist(a: number, b: number): number {
|
||||
const max = Math.PI * 2
|
||||
const da = (b - a) % max
|
||||
return ((2 * da) % max) - da
|
||||
}
|
||||
|
||||
static getSweep(C: Vector, A: Vector, B: Vector) {
|
||||
static getSweep(C: Vector, A: Vector, B: Vector): number {
|
||||
return Utils.shortAngleDist(Vector.ang(C, A), Vector.ang(C, B))
|
||||
}
|
||||
|
||||
static bez1d(a: number, b: number, c: number, d: number, t: number) {
|
||||
static bez1d(a: number, b: number, c: number, d: number, t: number): number {
|
||||
return (
|
||||
a * (1 - t) * (1 - t) * (1 - t) +
|
||||
3 * b * t * (1 - t) * (1 - t) +
|
||||
|
@ -126,7 +142,7 @@ export default class Utils {
|
|||
}
|
||||
}
|
||||
|
||||
static getExpandedBounds(a: Bounds, b: Bounds) {
|
||||
static getExpandedBounds(a: Bounds, b: Bounds): Bounds {
|
||||
const minX = Math.min(a.minX, b.minX),
|
||||
minY = Math.min(a.minY, b.minY),
|
||||
maxX = Math.max(a.maxX, b.maxX),
|
||||
|
@ -137,7 +153,7 @@ export default class Utils {
|
|||
return { minX, minY, maxX, maxY, width, height }
|
||||
}
|
||||
|
||||
static getCommonBounds(...b: Bounds[]) {
|
||||
static getCommonBounds(...b: Bounds[]): Bounds {
|
||||
if (b.length < 2) return b[0]
|
||||
|
||||
let bounds = b[0]
|
|
@ -26,43 +26,44 @@ export default class Vector {
|
|||
}
|
||||
}
|
||||
|
||||
set(v: Vector | Point) {
|
||||
set(v: Vector | Point): Vector {
|
||||
this.x = v.x
|
||||
this.y = v.y
|
||||
return this
|
||||
}
|
||||
|
||||
copy() {
|
||||
copy(): Vector {
|
||||
return new Vector(this)
|
||||
}
|
||||
|
||||
clone() {
|
||||
clone(): Vector {
|
||||
return this.copy()
|
||||
}
|
||||
|
||||
toArray() {
|
||||
toArray(): number[] {
|
||||
return [this.x, this.y]
|
||||
}
|
||||
|
||||
add(b: Vector) {
|
||||
add(b: Vector): Vector {
|
||||
this.x += b.x
|
||||
this.y += b.y
|
||||
return this
|
||||
}
|
||||
|
||||
static add(a: Vector, b: Vector) {
|
||||
static add(a: Vector, b: Vector): Vector {
|
||||
const n = new Vector(a)
|
||||
n.x += b.x
|
||||
n.y += b.y
|
||||
return n
|
||||
}
|
||||
|
||||
sub(b: Vector) {
|
||||
sub(b: Vector): Vector {
|
||||
this.x -= b.x
|
||||
this.y -= b.y
|
||||
return this
|
||||
}
|
||||
|
||||
static sub(a: Vector, b: Vector) {
|
||||
static sub(a: Vector, b: Vector): Vector {
|
||||
const n = new Vector(a)
|
||||
n.x -= b.x
|
||||
n.y -= b.y
|
||||
|
@ -71,7 +72,7 @@ export default class Vector {
|
|||
|
||||
mul(b: number): Vector
|
||||
mul(b: Vector): Vector
|
||||
mul(b: Vector | number) {
|
||||
mul(b: Vector | number): Vector {
|
||||
if (b instanceof Vector) {
|
||||
this.x *= b.x
|
||||
this.y *= b.y
|
||||
|
@ -82,17 +83,17 @@ export default class Vector {
|
|||
return this
|
||||
}
|
||||
|
||||
mulScalar(b: number) {
|
||||
mulScalar(b: number): Vector {
|
||||
return this.mul(b)
|
||||
}
|
||||
|
||||
static mulScalar(a: Vector, b: number) {
|
||||
static mulScalar(a: Vector, b: number): Vector {
|
||||
return Vector.mul(a, b)
|
||||
}
|
||||
|
||||
static mul(a: Vector, b: number): Vector
|
||||
static mul(a: Vector, b: Vector): Vector
|
||||
static mul(a: Vector, b: Vector | number) {
|
||||
static mul(a: Vector, b: Vector | number): Vector {
|
||||
const n = new Vector(a)
|
||||
if (b instanceof Vector) {
|
||||
n.x *= b.x
|
||||
|
@ -106,7 +107,7 @@ export default class Vector {
|
|||
|
||||
div(b: number): Vector
|
||||
div(b: Vector): Vector
|
||||
div(b: Vector | number) {
|
||||
div(b: Vector | number): Vector {
|
||||
if (b instanceof Vector) {
|
||||
if (b.x) {
|
||||
this.x /= b.x
|
||||
|
@ -125,7 +126,7 @@ export default class Vector {
|
|||
|
||||
static div(a: Vector, b: number): Vector
|
||||
static div(a: Vector, b: Vector): Vector
|
||||
static div(a: Vector, b: Vector | number) {
|
||||
static div(a: Vector, b: Vector | number): Vector {
|
||||
const n = new Vector(a)
|
||||
if (b instanceof Vector) {
|
||||
if (b.x) n.x /= b.x
|
||||
|
@ -139,112 +140,112 @@ export default class Vector {
|
|||
return n
|
||||
}
|
||||
|
||||
divScalar(b: number) {
|
||||
divScalar(b: number): Vector {
|
||||
return this.div(b)
|
||||
}
|
||||
|
||||
static divScalar(a: Vector, b: number) {
|
||||
static divScalar(a: Vector, b: number): Vector {
|
||||
return Vector.div(a, b)
|
||||
}
|
||||
|
||||
vec(b: Vector) {
|
||||
vec(b: Vector): Vector {
|
||||
const { x, y } = this
|
||||
this.x = b.x - x
|
||||
this.y = b.y - y
|
||||
return this
|
||||
}
|
||||
|
||||
static vec(a: Vector, b: Vector) {
|
||||
static vec(a: Vector, b: Vector): Vector {
|
||||
const n = new Vector(a)
|
||||
n.x = b.x - a.x
|
||||
n.y = b.y - a.y
|
||||
return n
|
||||
}
|
||||
|
||||
pry(b: Vector) {
|
||||
pry(b: Vector): number {
|
||||
return this.dpr(b) / b.len()
|
||||
}
|
||||
|
||||
static pry(a: Vector, b: Vector) {
|
||||
static pry(a: Vector, b: Vector): number {
|
||||
return a.dpr(b) / b.len()
|
||||
}
|
||||
|
||||
dpr(b: Vector) {
|
||||
dpr(b: Vector): number {
|
||||
return this.x * b.x + this.y * b.y
|
||||
}
|
||||
|
||||
static dpr(a: Vector, b: Vector) {
|
||||
static dpr(a: Vector, b: Vector): number {
|
||||
return a.x & (b.x + a.y * b.y)
|
||||
}
|
||||
|
||||
cpr(b: Vector) {
|
||||
cpr(b: Vector): number {
|
||||
return this.x * b.y - b.y * this.y
|
||||
}
|
||||
|
||||
static cpr(a: Vector, b: Vector) {
|
||||
static cpr(a: Vector, b: Vector): number {
|
||||
return a.x * b.y - b.y * a.y
|
||||
}
|
||||
|
||||
tangent(b: Vector) {
|
||||
tangent(b: Vector): Vector {
|
||||
return this.sub(b).uni()
|
||||
}
|
||||
|
||||
static tangent(a: Vector, b: Vector) {
|
||||
static tangent(a: Vector, b: Vector): Vector {
|
||||
const n = new Vector(a)
|
||||
return n.sub(b).uni()
|
||||
}
|
||||
|
||||
dist2(b: Vector) {
|
||||
dist2(b: Vector): number {
|
||||
return this.sub(b).len2()
|
||||
}
|
||||
|
||||
static dist2(a: Vector, b: Vector) {
|
||||
static dist2(a: Vector, b: Vector): number {
|
||||
const n = new Vector(a)
|
||||
return n.sub(b).len2()
|
||||
}
|
||||
|
||||
dist(b: Vector) {
|
||||
dist(b: Vector): number {
|
||||
return Math.hypot(b.y - this.y, b.x - this.x)
|
||||
}
|
||||
|
||||
static dist(a: Vector, b: Vector) {
|
||||
static dist(a: Vector, b: Vector): number {
|
||||
const n = new Vector(a)
|
||||
return Math.hypot(b.y - n.y, b.x - n.x)
|
||||
}
|
||||
|
||||
ang(b: Vector) {
|
||||
ang(b: Vector): number {
|
||||
return Math.atan2(b.y - this.y, b.x - this.x)
|
||||
}
|
||||
|
||||
static ang(a: Vector, b: Vector) {
|
||||
static ang(a: Vector, b: Vector): number {
|
||||
const n = new Vector(a)
|
||||
return Math.atan2(b.y - n.y, b.x - n.x)
|
||||
}
|
||||
|
||||
med(b: Vector) {
|
||||
med(b: Vector): Vector {
|
||||
return this.add(b).mul(0.5)
|
||||
}
|
||||
|
||||
static med(a: Vector, b: Vector) {
|
||||
static med(a: Vector, b: Vector): Vector {
|
||||
const n = new Vector(a)
|
||||
return n.add(b).mul(0.5)
|
||||
}
|
||||
|
||||
rot(r: number) {
|
||||
rot(r: number): Vector {
|
||||
const { x, y } = this
|
||||
this.x = x * Math.cos(r) - y * Math.sin(r)
|
||||
this.y = x * Math.sin(r) + y * Math.cos(r)
|
||||
return this
|
||||
}
|
||||
|
||||
static rot(a: Vector, r: number) {
|
||||
static rot(a: Vector, r: number): Vector {
|
||||
const n = new Vector(a)
|
||||
n.x = a.x * Math.cos(r) - a.y * Math.sin(r)
|
||||
n.y = a.x * Math.sin(r) + a.y * Math.cos(r)
|
||||
return n
|
||||
}
|
||||
|
||||
rotAround(b: Vector, r: number) {
|
||||
rotAround(b: Vector, r: number): Vector {
|
||||
const { x, y } = this
|
||||
const s = Math.sin(r)
|
||||
const c = Math.cos(r)
|
||||
|
@ -258,7 +259,7 @@ export default class Vector {
|
|||
return this
|
||||
}
|
||||
|
||||
static rotAround(a: Vector, b: Vector, r: number) {
|
||||
static rotAround(a: Vector, b: Vector, r: number): Vector {
|
||||
const n = new Vector(a)
|
||||
const s = Math.sin(r)
|
||||
const c = Math.cos(r)
|
||||
|
@ -272,173 +273,185 @@ export default class Vector {
|
|||
return n
|
||||
}
|
||||
|
||||
lrp(b: Vector, t: number) {
|
||||
lrp(b: Vector, t: number): Vector {
|
||||
const n = new Vector(this)
|
||||
this.vec(b).mul(t).add(n)
|
||||
return this
|
||||
}
|
||||
|
||||
static lrp(a: Vector, b: Vector, t: number) {
|
||||
static lrp(a: Vector, b: Vector, t: number): Vector {
|
||||
const n = new Vector(a)
|
||||
n.vec(b).mul(t).add(a)
|
||||
return n
|
||||
}
|
||||
|
||||
nudge(b: Vector, d: number) {
|
||||
nudge(b: Vector, d: number): Vector {
|
||||
this.add(b.mul(d))
|
||||
return this
|
||||
}
|
||||
|
||||
static nudge(a: Vector, b: Vector, d: number) {
|
||||
static nudge(a: Vector, b: Vector, d: number): Vector {
|
||||
const n = new Vector(a)
|
||||
return n.add(b.mul(d))
|
||||
}
|
||||
|
||||
nudgeToward(b: Vector, d: number) {
|
||||
nudgeToward(b: Vector, d: number): Vector {
|
||||
return this.nudge(Vector.vec(this, b).uni(), d)
|
||||
}
|
||||
|
||||
static nudgeToward(a: Vector, b: Vector, d: number) {
|
||||
static nudgeToward(a: Vector, b: Vector, d: number): Vector {
|
||||
return Vector.nudge(a, Vector.vec(a, b).uni(), d)
|
||||
}
|
||||
|
||||
int(b: Vector, from: number, to: number, s: number) {
|
||||
int(b: Vector, from: number, to: number, s: number): Vector {
|
||||
const t = (Math.max(from, to) - from) / (to - from)
|
||||
this.add(Vector.mul(this, 1 - t).add(Vector.mul(b, s)))
|
||||
return this
|
||||
}
|
||||
|
||||
static int(a: Vector, b: Vector, from: number, to: number, s: number) {
|
||||
static int(
|
||||
a: Vector,
|
||||
b: Vector,
|
||||
from: number,
|
||||
to: number,
|
||||
s: number
|
||||
): Vector {
|
||||
const n = new Vector(a)
|
||||
const t = (Math.max(from, to) - from) / (to - from)
|
||||
n.add(Vector.mul(a, 1 - t).add(Vector.mul(b, s)))
|
||||
return n
|
||||
}
|
||||
|
||||
equals(b: Vector) {
|
||||
equals(b: Vector): boolean {
|
||||
return this.x === b.x && this.y === b.y
|
||||
}
|
||||
|
||||
static equals(a: Vector, b: Vector) {
|
||||
static equals(a: Vector, b: Vector): boolean {
|
||||
return a.x === b.x && a.y === b.y
|
||||
}
|
||||
|
||||
abs() {
|
||||
abs(): Vector {
|
||||
this.x = Math.abs(this.x)
|
||||
this.y = Math.abs(this.y)
|
||||
return this
|
||||
}
|
||||
|
||||
static abs(a: Vector) {
|
||||
static abs(a: Vector): Vector {
|
||||
const n = new Vector(a)
|
||||
n.x = Math.abs(n.x)
|
||||
n.y = Math.abs(n.y)
|
||||
return n
|
||||
}
|
||||
|
||||
len() {
|
||||
len(): number {
|
||||
return Math.hypot(this.x, this.y)
|
||||
}
|
||||
|
||||
static len(a: Vector) {
|
||||
static len(a: Vector): number {
|
||||
return Math.hypot(a.x, a.y)
|
||||
}
|
||||
|
||||
len2() {
|
||||
len2(): number {
|
||||
return this.x * this.x + this.y * this.y
|
||||
}
|
||||
|
||||
static len2(a: Vector) {
|
||||
static len2(a: Vector): number {
|
||||
return a.x * a.x + a.y * a.y
|
||||
}
|
||||
|
||||
per() {
|
||||
per(): Vector {
|
||||
const t = this.x
|
||||
this.x = this.y
|
||||
this.y = -t
|
||||
return this
|
||||
}
|
||||
|
||||
static per(a: Vector) {
|
||||
static per(a: Vector): Vector {
|
||||
const n = new Vector(a)
|
||||
n.x = n.y
|
||||
n.y = -a.x
|
||||
return n
|
||||
}
|
||||
|
||||
neg() {
|
||||
neg(): Vector {
|
||||
this.x *= -1
|
||||
this.y *= -1
|
||||
return this
|
||||
}
|
||||
|
||||
static neg(v: Vector) {
|
||||
static neg(v: Vector): Vector {
|
||||
const n = new Vector(v)
|
||||
n.x *= -1
|
||||
n.y *= -1
|
||||
return n
|
||||
}
|
||||
|
||||
uni() {
|
||||
uni(): Vector {
|
||||
return this.div(this.len())
|
||||
}
|
||||
|
||||
static uni(v: Vector) {
|
||||
static uni(v: Vector): Vector {
|
||||
const n = new Vector(v)
|
||||
return n.div(n.len())
|
||||
}
|
||||
|
||||
normalize() {
|
||||
normalize(): Vector {
|
||||
return this.uni()
|
||||
}
|
||||
|
||||
static normalize(v: Vector) {
|
||||
static normalize(v: Vector): Vector {
|
||||
return Vector.uni(v)
|
||||
}
|
||||
|
||||
isLeft(center: Vector, b: Vector) {
|
||||
isLeft(center: Vector, b: Vector): number {
|
||||
return (
|
||||
(center.x - this.x) * (b.y - this.y) - (b.x - this.x) * (center.y - b.y)
|
||||
)
|
||||
}
|
||||
|
||||
static isLeft(center: Vector, a: Vector, b: Vector) {
|
||||
static isLeft(center: Vector, a: Vector, b: Vector): number {
|
||||
return (center.x - a.x) * (b.y - a.y) - (b.x - a.x) * (center.y - b.y)
|
||||
}
|
||||
|
||||
static ang3(center: Vector, a: Vector, b: Vector) {
|
||||
static ang3(center: Vector, a: Vector, b: Vector): number {
|
||||
const v1 = Vector.vec(center, a)
|
||||
const v2 = Vector.vec(center, b)
|
||||
return Vector.ang(v1, v2)
|
||||
}
|
||||
|
||||
static clockwise(center: Vector, a: Vector, b: Vector) {
|
||||
static clockwise(center: Vector, a: Vector, b: Vector): boolean {
|
||||
return Vector.isLeft(center, a, b) > 0
|
||||
}
|
||||
|
||||
static cast(v: Point | Vector) {
|
||||
static cast(v: Point | Vector): Vector {
|
||||
return 'cast' in v ? v : new Vector(v)
|
||||
}
|
||||
|
||||
static from(v: Vector) {
|
||||
static from(v: Vector): Vector {
|
||||
return new Vector(v)
|
||||
}
|
||||
|
||||
nearestPointOnLineThroughPoint(b: Vector, u: Vector) {
|
||||
nearestPointOnLineThroughPoint(b: Vector, u: Vector): Vector {
|
||||
return this.clone().add(u.clone().mul(Vector.sub(this, b).pry(u)))
|
||||
}
|
||||
|
||||
static nearestPointOnLineThroughPoint(a: Vector, b: Vector, u: Vector) {
|
||||
static nearestPointOnLineThroughPoint(
|
||||
a: Vector,
|
||||
b: Vector,
|
||||
u: Vector
|
||||
): Vector {
|
||||
return a.clone().add(u.clone().mul(Vector.sub(a, b).pry(u)))
|
||||
}
|
||||
|
||||
distanceToLineThroughPoint(b: Vector, u: Vector) {
|
||||
distanceToLineThroughPoint(b: Vector, u: Vector): number {
|
||||
return this.dist(Vector.nearestPointOnLineThroughPoint(b, u, this))
|
||||
}
|
||||
|
||||
static distanceToLineThroughPoint(a: Vector, b: Vector, u: Vector) {
|
||||
static distanceToLineThroughPoint(a: Vector, b: Vector, u: Vector): number {
|
||||
return a.dist(Vector.nearestPointOnLineThroughPoint(b, u, a))
|
||||
}
|
||||
|
||||
nearestPointOnLineSegment(p0: Vector, p1: Vector, clamp = true) {
|
||||
nearestPointOnLineSegment(p0: Vector, p1: Vector, clamp = true): Vector {
|
||||
return Vector.nearestPointOnLineSegment(this, p0, p1, clamp)
|
||||
}
|
||||
|
||||
|
@ -447,7 +460,7 @@ export default class Vector {
|
|||
p0: Vector,
|
||||
p1: Vector,
|
||||
clamp = true
|
||||
) {
|
||||
): Vector {
|
||||
const delta = Vector.sub(p1, p0)
|
||||
const length = delta.len()
|
||||
const u = Vector.div(delta, length)
|
||||
|
@ -465,7 +478,7 @@ export default class Vector {
|
|||
return pt
|
||||
}
|
||||
|
||||
distanceToLineSegment(p0: Vector, p1: Vector, clamp = true) {
|
||||
distanceToLineSegment(p0: Vector, p1: Vector, clamp = true): number {
|
||||
return Vector.distanceToLineSegment(this, p0, p1, clamp)
|
||||
}
|
||||
|
||||
|
@ -474,7 +487,7 @@ export default class Vector {
|
|||
p0: Vector,
|
||||
p1: Vector,
|
||||
clamp = true
|
||||
) {
|
||||
): number {
|
||||
return Vector.dist(a, Vector.nearestPointOnLineSegment(a, p0, p1, clamp))
|
||||
}
|
||||
}
|
|
@ -2,9 +2,9 @@ import Command from './command'
|
|||
import history from '../history'
|
||||
import { AlignType, Data } from 'types'
|
||||
import { getCommonBounds, getPage, getSelectedShapes } from 'utils/utils'
|
||||
import { getShapeUtils } from 'lib/shape-utils'
|
||||
import { getShapeUtils } from 'state/shape-utils'
|
||||
|
||||
export default function alignCommand(data: Data, type: AlignType) {
|
||||
export default function alignCommand(data: Data, type: AlignType): void {
|
||||
const { currentPageId } = data
|
||||
const selectedShapes = getSelectedShapes(data)
|
||||
const entries = selectedShapes.map(
|
||||
|
@ -25,7 +25,7 @@ export default function alignCommand(data: Data, type: AlignType) {
|
|||
|
||||
switch (type) {
|
||||
case AlignType.Top: {
|
||||
for (let id in boundsForShapes) {
|
||||
for (const id in boundsForShapes) {
|
||||
const shape = shapes[id]
|
||||
getShapeUtils(shape).translateTo(shape, [
|
||||
shape.point[0],
|
||||
|
@ -35,7 +35,7 @@ export default function alignCommand(data: Data, type: AlignType) {
|
|||
break
|
||||
}
|
||||
case AlignType.CenterVertical: {
|
||||
for (let id in boundsForShapes) {
|
||||
for (const id in boundsForShapes) {
|
||||
const shape = shapes[id]
|
||||
getShapeUtils(shape).translateTo(shape, [
|
||||
shape.point[0],
|
||||
|
@ -45,7 +45,7 @@ export default function alignCommand(data: Data, type: AlignType) {
|
|||
break
|
||||
}
|
||||
case AlignType.Bottom: {
|
||||
for (let id in boundsForShapes) {
|
||||
for (const id in boundsForShapes) {
|
||||
const shape = shapes[id]
|
||||
getShapeUtils(shape).translateTo(shape, [
|
||||
shape.point[0],
|
||||
|
@ -55,7 +55,7 @@ export default function alignCommand(data: Data, type: AlignType) {
|
|||
break
|
||||
}
|
||||
case AlignType.Left: {
|
||||
for (let id in boundsForShapes) {
|
||||
for (const id in boundsForShapes) {
|
||||
const shape = shapes[id]
|
||||
getShapeUtils(shape).translateTo(shape, [
|
||||
commonBounds.minX,
|
||||
|
@ -65,7 +65,7 @@ export default function alignCommand(data: Data, type: AlignType) {
|
|||
break
|
||||
}
|
||||
case AlignType.CenterHorizontal: {
|
||||
for (let id in boundsForShapes) {
|
||||
for (const id in boundsForShapes) {
|
||||
const shape = shapes[id]
|
||||
getShapeUtils(shape).translateTo(shape, [
|
||||
midX - boundsForShapes[id].width / 2,
|
||||
|
@ -75,7 +75,7 @@ export default function alignCommand(data: Data, type: AlignType) {
|
|||
break
|
||||
}
|
||||
case AlignType.Right: {
|
||||
for (let id in boundsForShapes) {
|
||||
for (const id in boundsForShapes) {
|
||||
const shape = shapes[id]
|
||||
getShapeUtils(shape).translateTo(shape, [
|
||||
commonBounds.maxX - boundsForShapes[id].width,
|
||||
|
@ -88,7 +88,7 @@ export default function alignCommand(data: Data, type: AlignType) {
|
|||
},
|
||||
undo(data) {
|
||||
const { shapes } = getPage(data, currentPageId)
|
||||
for (let id in boundsForShapes) {
|
||||
for (const id in boundsForShapes) {
|
||||
const shape = shapes[id]
|
||||
const initialBounds = boundsForShapes[id]
|
||||
getShapeUtils(shape).translateTo(shape, [
|
||||
|
|
|
@ -8,7 +8,7 @@ export default function arrowCommand(
|
|||
data: Data,
|
||||
before: ArrowSnapshot,
|
||||
after: ArrowSnapshot
|
||||
) {
|
||||
): void {
|
||||
history.execute(
|
||||
data,
|
||||
new Command({
|
||||
|
|
|
@ -3,7 +3,7 @@ import history from '../history'
|
|||
import { Data } from 'types'
|
||||
import storage from 'state/storage'
|
||||
|
||||
export default function changePage(data: Data, toPageId: string) {
|
||||
export default function changePage(data: Data, toPageId: string): void {
|
||||
const { currentPageId: fromPageId } = data
|
||||
|
||||
history.execute(
|
||||
|
|
|
@ -40,7 +40,7 @@ export class BaseCommand<T extends any> {
|
|||
}
|
||||
}
|
||||
|
||||
undo = (data: T) => {
|
||||
undo = (data: T): void => {
|
||||
if (this.manualSelection) {
|
||||
this.undoFn(data)
|
||||
return
|
||||
|
@ -52,7 +52,7 @@ export class BaseCommand<T extends any> {
|
|||
this.restoreBeforeSelectionState(data)
|
||||
}
|
||||
|
||||
redo = (data: T, initial = false) => {
|
||||
redo = (data: T, initial = false): void => {
|
||||
if (this.manualSelection) {
|
||||
this.doFn(data, initial)
|
||||
|
||||
|
@ -82,7 +82,7 @@ export class BaseCommand<T extends any> {
|
|||
* the app.
|
||||
*/
|
||||
export default class Command extends BaseCommand<Data> {
|
||||
saveSelectionState = (data: Data) => {
|
||||
saveSelectionState = (data: Data): ((next: Data) => void) => {
|
||||
const { currentPageId } = data
|
||||
const selectedIds = setToArray(getSelectedIds(data))
|
||||
return (next: Data) => {
|
||||
|
|
|
@ -5,7 +5,7 @@ import { uniqueId } from 'utils/utils'
|
|||
import { current } from 'immer'
|
||||
import storage from 'state/storage'
|
||||
|
||||
export default function createPage(data: Data, goToPage = true) {
|
||||
export default function createPage(data: Data, goToPage = true): void {
|
||||
const snapshot = getSnapshot(data)
|
||||
|
||||
history.execute(
|
||||
|
|
|
@ -2,10 +2,9 @@ import Command from './command'
|
|||
import history from '../history'
|
||||
import { Data } from 'types'
|
||||
import { current } from 'immer'
|
||||
import vec from 'utils/vec'
|
||||
import storage from 'state/storage'
|
||||
|
||||
export default function deletePage(data: Data, pageId: string) {
|
||||
export default function deletePage(data: Data, pageId: string): void {
|
||||
const snapshot = getSnapshot(data, pageId)
|
||||
|
||||
history.execute(
|
||||
|
|
|
@ -1,21 +1,16 @@
|
|||
import Command from './command'
|
||||
import history from '../history'
|
||||
import { TranslateSnapshot } from 'state/sessions/translate-session'
|
||||
import { Data, ShapeType } from 'types'
|
||||
import { Data } from 'types'
|
||||
import {
|
||||
getDocumentBranch,
|
||||
getPage,
|
||||
getPageState,
|
||||
getSelectedIds,
|
||||
getSelectedShapes,
|
||||
setSelectedIds,
|
||||
setToArray,
|
||||
updateParents,
|
||||
} from 'utils/utils'
|
||||
import { current } from 'immer'
|
||||
import { getShapeUtils } from 'lib/shape-utils'
|
||||
import { getShapeUtils } from 'state/shape-utils'
|
||||
|
||||
export default function deleteSelected(data: Data) {
|
||||
export default function deleteSelected(data: Data): void {
|
||||
const { currentPageId } = data
|
||||
|
||||
const selectedShapes = getSelectedShapes(data)
|
||||
|
@ -43,7 +38,7 @@ export default function deleteSelected(data: Data) {
|
|||
do(data) {
|
||||
const page = getPage(data, currentPageId)
|
||||
|
||||
for (let id of selectedIdsArr) {
|
||||
for (const id of selectedIdsArr) {
|
||||
const shape = page.shapes[id]
|
||||
if (!shape) {
|
||||
console.error('no shape ' + id)
|
||||
|
@ -65,7 +60,7 @@ export default function deleteSelected(data: Data) {
|
|||
}
|
||||
}
|
||||
|
||||
for (let shape of childrenToDelete) {
|
||||
for (const shape of childrenToDelete) {
|
||||
delete page.shapes[shape.id]
|
||||
}
|
||||
|
||||
|
@ -74,11 +69,11 @@ export default function deleteSelected(data: Data) {
|
|||
undo(data) {
|
||||
const page = getPage(data, currentPageId)
|
||||
|
||||
for (let shape of childrenToDelete) {
|
||||
for (const shape of childrenToDelete) {
|
||||
page.shapes[shape.id] = shape
|
||||
}
|
||||
|
||||
for (let shape of childrenToDelete) {
|
||||
for (const shape of childrenToDelete) {
|
||||
if (shape.parentId !== data.currentPageId) {
|
||||
const parent = page.shapes[shape.parentId]
|
||||
getShapeUtils(parent)
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
import Command from "./command"
|
||||
import history from "../history"
|
||||
import { DirectionSnapshot } from "state/sessions/direction-session"
|
||||
import { Data, LineShape, RayShape } from "types"
|
||||
import { getPage } from "utils/utils"
|
||||
import Command from './command'
|
||||
import history from '../history'
|
||||
import { DirectionSnapshot } from 'state/sessions/direction-session'
|
||||
import { Data, LineShape, RayShape } from 'types'
|
||||
import { getPage } from 'utils/utils'
|
||||
|
||||
export default function directCommand(
|
||||
data: Data,
|
||||
before: DirectionSnapshot,
|
||||
after: DirectionSnapshot
|
||||
) {
|
||||
): void {
|
||||
history.execute(
|
||||
data,
|
||||
new Command({
|
||||
name: "set_direction",
|
||||
category: "canvas",
|
||||
name: 'set_direction',
|
||||
category: 'canvas',
|
||||
do(data) {
|
||||
const { shapes } = getPage(data)
|
||||
|
||||
for (let { id, direction } of after.shapes) {
|
||||
for (const { id, direction } of after.shapes) {
|
||||
const shape = shapes[id] as RayShape | LineShape
|
||||
|
||||
shape.direction = direction
|
||||
|
@ -26,7 +26,7 @@ export default function directCommand(
|
|||
undo(data) {
|
||||
const { shapes } = getPage(data, before.currentPageId)
|
||||
|
||||
for (let { id, direction } of after.shapes) {
|
||||
for (const { id, direction } of after.shapes) {
|
||||
const shape = shapes[id] as RayShape | LineShape
|
||||
|
||||
shape.direction = direction
|
||||
|
|
|
@ -7,9 +7,12 @@ import {
|
|||
getPage,
|
||||
getSelectedShapes,
|
||||
} from 'utils/utils'
|
||||
import { getShapeUtils } from 'lib/shape-utils'
|
||||
import { getShapeUtils } from 'state/shape-utils'
|
||||
|
||||
export default function distributeCommand(data: Data, type: DistributeType) {
|
||||
export default function distributeCommand(
|
||||
data: Data,
|
||||
type: DistributeType
|
||||
): void {
|
||||
const { currentPageId } = data
|
||||
|
||||
const selectedShapes = getSelectedShapes(data).filter(
|
||||
|
@ -130,7 +133,7 @@ export default function distributeCommand(data: Data, type: DistributeType) {
|
|||
},
|
||||
undo(data) {
|
||||
const { shapes } = getPage(data, currentPageId)
|
||||
for (let id in boundsForShapes) {
|
||||
for (const id in boundsForShapes) {
|
||||
const shape = shapes[id]
|
||||
const initialBounds = boundsForShapes[id]
|
||||
getShapeUtils(shape).translateTo(shape, [
|
||||
|
|
|
@ -4,7 +4,7 @@ import { Data, DrawShape } from 'types'
|
|||
import { getPage, setSelectedIds } from 'utils/utils'
|
||||
import { current } from 'immer'
|
||||
|
||||
export default function drawCommand(data: Data, id: string) {
|
||||
export default function drawCommand(data: Data, id: string): void {
|
||||
const restoreShape = getPage(current(data)).shapes[id] as DrawShape
|
||||
|
||||
history.execute(
|
||||
|
|
|
@ -4,7 +4,6 @@ import { Data } from 'types'
|
|||
import {
|
||||
getCurrentCamera,
|
||||
getPage,
|
||||
getSelectedIds,
|
||||
getSelectedShapes,
|
||||
setSelectedIds,
|
||||
} from 'utils/utils'
|
||||
|
@ -12,7 +11,7 @@ import { uniqueId } from 'utils/utils'
|
|||
import { current } from 'immer'
|
||||
import vec from 'utils/vec'
|
||||
|
||||
export default function duplicateCommand(data: Data) {
|
||||
export default function duplicateCommand(data: Data): void {
|
||||
const { currentPageId } = data
|
||||
const selectedShapes = getSelectedShapes(current(data))
|
||||
const duplicates = selectedShapes.map((shape) => ({
|
||||
|
|
|
@ -3,13 +3,13 @@ import history from '../history'
|
|||
import { Data } from 'types'
|
||||
import { getPage } from 'utils/utils'
|
||||
import { EditSnapshot } from 'state/sessions/edit-session'
|
||||
import { getShapeUtils } from 'lib/shape-utils'
|
||||
import { getShapeUtils } from 'state/shape-utils'
|
||||
|
||||
export default function editCommand(
|
||||
data: Data,
|
||||
before: EditSnapshot,
|
||||
after: EditSnapshot
|
||||
) {
|
||||
): void {
|
||||
history.execute(
|
||||
data,
|
||||
new Command({
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import Command from './command'
|
||||
import history from '../history'
|
||||
import { CodeControl, Data, Shape } from 'types'
|
||||
import { Data, Shape } from 'types'
|
||||
import { current } from 'immer'
|
||||
import { getPage, getSelectedIds, setSelectedIds } from 'utils/utils'
|
||||
import { getPage, setSelectedIds } from 'utils/utils'
|
||||
|
||||
export default function generateCommand(
|
||||
data: Data,
|
||||
currentPageId: string,
|
||||
generatedShapes: Shape[]
|
||||
) {
|
||||
): void {
|
||||
const cData = current(data)
|
||||
const page = getPage(cData)
|
||||
|
||||
|
@ -19,14 +19,14 @@ export default function generateCommand(
|
|||
)
|
||||
|
||||
// Remove previous generated shapes
|
||||
for (let id in currentShapes) {
|
||||
for (const id in currentShapes) {
|
||||
if (currentShapes[id].isGenerated) {
|
||||
delete currentShapes[id]
|
||||
}
|
||||
}
|
||||
|
||||
// Add new ones
|
||||
for (let shape of generatedShapes) {
|
||||
for (const shape of generatedShapes) {
|
||||
currentShapes[shape.id] = shape
|
||||
}
|
||||
|
||||
|
@ -41,14 +41,14 @@ export default function generateCommand(
|
|||
setSelectedIds(data, [])
|
||||
|
||||
// Remove previous generated shapes
|
||||
for (let id in shapes) {
|
||||
for (const id in shapes) {
|
||||
if (shapes[id].isGenerated) {
|
||||
delete shapes[id]
|
||||
}
|
||||
}
|
||||
|
||||
// Add new generated shapes
|
||||
for (let shape of generatedShapes) {
|
||||
for (const shape of generatedShapes) {
|
||||
shapes[shape.id] = shape
|
||||
}
|
||||
},
|
||||
|
@ -56,14 +56,14 @@ export default function generateCommand(
|
|||
const { shapes } = getPage(data)
|
||||
|
||||
// Remove generated shapes
|
||||
for (let id in shapes) {
|
||||
for (const id in shapes) {
|
||||
if (shapes[id].isGenerated) {
|
||||
delete shapes[id]
|
||||
}
|
||||
}
|
||||
|
||||
// Restore previous generated shapes
|
||||
for (let shape of prevGeneratedShapes) {
|
||||
for (const shape of prevGeneratedShapes) {
|
||||
shapes[shape.id] = shape
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import Command from './command'
|
||||
import history from '../history'
|
||||
import { Data, GroupShape, Shape, ShapeType } from 'types'
|
||||
import { Data, GroupShape, ShapeType } from 'types'
|
||||
import {
|
||||
getCommonBounds,
|
||||
getPage,
|
||||
|
@ -10,10 +10,10 @@ import {
|
|||
setSelectedIds,
|
||||
} from 'utils/utils'
|
||||
import { current } from 'immer'
|
||||
import { createShape, getShapeUtils } from 'lib/shape-utils'
|
||||
import { createShape, getShapeUtils } from 'state/shape-utils'
|
||||
import commands from '.'
|
||||
|
||||
export default function groupCommand(data: Data) {
|
||||
export default function groupCommand(data: Data): void {
|
||||
const cData = current(data)
|
||||
const { currentPageId } = cData
|
||||
|
||||
|
@ -28,15 +28,9 @@ export default function groupCommand(data: Data) {
|
|||
)
|
||||
|
||||
let newGroupParentId: string
|
||||
let newGroupShape: GroupShape
|
||||
let newGroupChildIndex: number
|
||||
|
||||
const initialShapeIds = initialShapes.map((s) => s.id)
|
||||
|
||||
const parentIds = Array.from(
|
||||
new Set(initialShapes.map((s) => s.parentId)).values()
|
||||
)
|
||||
|
||||
const commonBounds = getCommonBounds(
|
||||
...initialShapes.map((shape) =>
|
||||
getShapeUtils(shape).getRotatedBounds(shape)
|
||||
|
@ -64,7 +58,7 @@ export default function groupCommand(data: Data) {
|
|||
// Find the least-deep parent among the shapes and add the group as a child
|
||||
let minDepth = Infinity
|
||||
|
||||
for (let parentId of initialShapes.map((shape) => shape.parentId)) {
|
||||
for (const parentId of initialShapes.map((shape) => shape.parentId)) {
|
||||
const depth = getShapeDepth(data, parentId)
|
||||
if (depth < minDepth) {
|
||||
minDepth = depth
|
||||
|
@ -73,7 +67,7 @@ export default function groupCommand(data: Data) {
|
|||
}
|
||||
}
|
||||
|
||||
newGroupShape = createShape(ShapeType.Group, {
|
||||
const newGroupShape = createShape(ShapeType.Group, {
|
||||
parentId: newGroupParentId,
|
||||
point: [commonBounds.minX, commonBounds.minY],
|
||||
size: [commonBounds.width, commonBounds.height],
|
||||
|
|
|
@ -3,22 +3,19 @@ import history from '../history'
|
|||
import { Data } from 'types'
|
||||
import { getPage } from 'utils/utils'
|
||||
import { HandleSnapshot } from 'state/sessions/handle-session'
|
||||
import { getShapeUtils } from 'lib/shape-utils'
|
||||
import vec from 'utils/vec'
|
||||
import { getShapeUtils } from 'state/shape-utils'
|
||||
|
||||
export default function handleCommand(
|
||||
data: Data,
|
||||
before: HandleSnapshot,
|
||||
after: HandleSnapshot
|
||||
) {
|
||||
): void {
|
||||
history.execute(
|
||||
data,
|
||||
new Command({
|
||||
name: 'moved_handle',
|
||||
category: 'canvas',
|
||||
do(data, isInitial) {
|
||||
// if (isInitial) return
|
||||
|
||||
do(data) {
|
||||
const { initialShape, currentPageId } = after
|
||||
|
||||
const page = getPage(data, currentPageId)
|
||||
|
@ -27,18 +24,6 @@ export default function handleCommand(
|
|||
getShapeUtils(shape)
|
||||
.onHandleChange(shape, initialShape.handles)
|
||||
.onSessionComplete(shape)
|
||||
|
||||
// const bounds = getShapeUtils(shape).getBounds(shape)
|
||||
|
||||
// const offset = vec.sub([bounds.minX, bounds.minY], shape.point)
|
||||
|
||||
// getShapeUtils(shape).translateTo(shape, vec.add(shape.point, offset))
|
||||
|
||||
// const { start, end, bend } = page.shapes[initialShape.id].handles
|
||||
|
||||
// start.point = vec.sub(start.point, offset)
|
||||
// end.point = vec.sub(end.point, offset)
|
||||
// bend.point = vec.sub(bend.point, offset)
|
||||
},
|
||||
undo(data) {
|
||||
const { initialShape, currentPageId } = before
|
||||
|
|
|
@ -6,16 +6,13 @@ import {
|
|||
getPage,
|
||||
getPageState,
|
||||
getSelectedIds,
|
||||
getSelectedShapes,
|
||||
getTopParentId,
|
||||
setToArray,
|
||||
uniqueArray,
|
||||
} from 'utils/utils'
|
||||
import { getShapeUtils } from 'lib/shape-utils'
|
||||
import vec from 'utils/vec'
|
||||
import { getShapeUtils } from 'state/shape-utils'
|
||||
import storage from 'state/storage'
|
||||
|
||||
export default function moveToPageCommand(data: Data, newPageId: string) {
|
||||
export default function moveToPageCommand(data: Data, newPageId: string): void {
|
||||
const { currentPageId: oldPageId } = data
|
||||
const oldPage = getPage(data)
|
||||
const selectedIds = setToArray(getSelectedIds(data))
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue