kopia lustrzana https://github.com/Tldraw/Tldraw
big cleanup
rodzic
daa44f9911
commit
864ded959a
6
.babelrc
6
.babelrc
|
@ -1,7 +1,3 @@
|
|||
{
|
||||
"env": {
|
||||
"test": {
|
||||
"presets": ["next/babel"]
|
||||
}
|
||||
}
|
||||
"presets": ["next/babel"]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
**/node_modules/*
|
||||
**/out/*
|
||||
**/.next/*
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -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
Ładowanie…
Reference in New Issue