adds hit testing for hovers
This commit is contained in:
parent
b8d3b35b07
commit
afa8f53dff
25 changed files with 1034 additions and 83 deletions
|
@ -16,8 +16,8 @@ export default function BoundsBg() {
|
|||
ref={rBounds}
|
||||
x={minX}
|
||||
y={minY}
|
||||
width={width}
|
||||
height={height}
|
||||
width={Math.max(1, width)}
|
||||
height={Math.max(1, height)}
|
||||
onPointerDown={(e) => {
|
||||
if (e.buttons !== 1) return
|
||||
e.stopPropagation()
|
||||
|
|
|
@ -16,8 +16,6 @@ export default function Bounds() {
|
|||
const p = 4 / zoom
|
||||
const cp = p * 2
|
||||
|
||||
if (width < p || height < p) return null
|
||||
|
||||
return (
|
||||
<g pointerEvents={isBrushing ? "none" : "all"}>
|
||||
<StyledBounds
|
||||
|
@ -27,66 +25,62 @@ export default function Bounds() {
|
|||
height={height}
|
||||
pointerEvents="none"
|
||||
/>
|
||||
{width * zoom > 8 && height * zoom > 8 && (
|
||||
<>
|
||||
<EdgeHorizontal
|
||||
x={minX + p}
|
||||
y={minY}
|
||||
width={Math.max(0, width - p * 2)}
|
||||
height={p}
|
||||
edge={TransformEdge.Top}
|
||||
/>
|
||||
<EdgeVertical
|
||||
x={maxX}
|
||||
y={minY + p}
|
||||
width={p}
|
||||
height={Math.max(0, height - p * 2)}
|
||||
edge={TransformEdge.Right}
|
||||
/>
|
||||
<EdgeHorizontal
|
||||
x={minX + p}
|
||||
y={maxY}
|
||||
width={Math.max(0, width - p * 2)}
|
||||
height={p}
|
||||
edge={TransformEdge.Bottom}
|
||||
/>
|
||||
<EdgeVertical
|
||||
x={minX}
|
||||
y={minY + p}
|
||||
width={p}
|
||||
height={Math.max(0, height - p * 2)}
|
||||
edge={TransformEdge.Left}
|
||||
/>
|
||||
<Corner
|
||||
x={minX}
|
||||
y={minY}
|
||||
width={cp}
|
||||
height={cp}
|
||||
corner={TransformCorner.TopLeft}
|
||||
/>
|
||||
<Corner
|
||||
x={maxX}
|
||||
y={minY}
|
||||
width={cp}
|
||||
height={cp}
|
||||
corner={TransformCorner.TopRight}
|
||||
/>
|
||||
<Corner
|
||||
x={maxX}
|
||||
y={maxY}
|
||||
width={cp}
|
||||
height={cp}
|
||||
corner={TransformCorner.BottomRight}
|
||||
/>
|
||||
<Corner
|
||||
x={minX}
|
||||
y={maxY}
|
||||
width={cp}
|
||||
height={cp}
|
||||
corner={TransformCorner.BottomLeft}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<EdgeHorizontal
|
||||
x={minX + p}
|
||||
y={minY}
|
||||
width={Math.max(0, width - p * 2)}
|
||||
height={p}
|
||||
edge={TransformEdge.Top}
|
||||
/>
|
||||
<EdgeVertical
|
||||
x={maxX}
|
||||
y={minY + p}
|
||||
width={p}
|
||||
height={Math.max(0, height - p * 2)}
|
||||
edge={TransformEdge.Right}
|
||||
/>
|
||||
<EdgeHorizontal
|
||||
x={minX + p}
|
||||
y={maxY}
|
||||
width={Math.max(0, width - p * 2)}
|
||||
height={p}
|
||||
edge={TransformEdge.Bottom}
|
||||
/>
|
||||
<EdgeVertical
|
||||
x={minX}
|
||||
y={minY + p}
|
||||
width={p}
|
||||
height={Math.max(0, height - p * 2)}
|
||||
edge={TransformEdge.Left}
|
||||
/>
|
||||
<Corner
|
||||
x={minX}
|
||||
y={minY}
|
||||
width={cp}
|
||||
height={cp}
|
||||
corner={TransformCorner.TopLeft}
|
||||
/>
|
||||
<Corner
|
||||
x={maxX}
|
||||
y={minY}
|
||||
width={cp}
|
||||
height={cp}
|
||||
corner={TransformCorner.TopRight}
|
||||
/>
|
||||
<Corner
|
||||
x={maxX}
|
||||
y={maxY}
|
||||
width={cp}
|
||||
height={cp}
|
||||
corner={TransformCorner.BottomRight}
|
||||
/>
|
||||
<Corner
|
||||
x={minX}
|
||||
y={maxY}
|
||||
width={cp}
|
||||
height={cp}
|
||||
corner={TransformCorner.BottomLeft}
|
||||
/>
|
||||
</g>
|
||||
)
|
||||
}
|
||||
|
@ -142,6 +136,7 @@ function Corner({
|
|||
rCorner.current.setPointerCapture(e.pointerId)
|
||||
state.send("POINTED_BOUNDS_CORNER", inputs.pointerDown(e, corner))
|
||||
}}
|
||||
onPointerCancelCapture={() => console.log("oops")}
|
||||
onPointerUp={(e) => {
|
||||
e.stopPropagation()
|
||||
rCorner.current.releasePointerCapture(e.pointerId)
|
||||
|
|
|
@ -7,6 +7,7 @@ import styled from "styles"
|
|||
function Shape({ id }: { id: string }) {
|
||||
const rGroup = useRef<SVGGElement>(null)
|
||||
|
||||
const isHovered = useSelector((state) => state.data.hoveredId === id)
|
||||
const isSelected = useSelector((state) => state.values.selectedIds.has(id))
|
||||
|
||||
const shape = useSelector(
|
||||
|
@ -32,23 +33,35 @@ function Shape({ id }: { id: string }) {
|
|||
)
|
||||
|
||||
const handlePointerEnter = useCallback(
|
||||
() => state.send("HOVERED_SHAPE", { id }),
|
||||
[id]
|
||||
(e: React.PointerEvent) => {
|
||||
state.send("HOVERED_SHAPE", inputs.pointerEnter(e, id))
|
||||
},
|
||||
[id, shape]
|
||||
)
|
||||
|
||||
const handlePointerMove = useCallback(
|
||||
(e: React.PointerEvent) => {
|
||||
state.send("MOVED_OVER_SHAPE", inputs.pointerEnter(e, id))
|
||||
},
|
||||
[id, shape]
|
||||
)
|
||||
|
||||
const handlePointerLeave = useCallback(
|
||||
() => state.send("UNHOVERED_SHAPE", { id }),
|
||||
() => state.send("UNHOVERED_SHAPE", { target: id }),
|
||||
[id]
|
||||
)
|
||||
|
||||
return (
|
||||
<StyledGroup
|
||||
ref={rGroup}
|
||||
isHovered={isHovered}
|
||||
isSelected={isSelected}
|
||||
transform={`translate(${shape.point})`}
|
||||
onPointerDown={handlePointerDown}
|
||||
onPointerUp={handlePointerUp}
|
||||
onPointerEnter={handlePointerEnter}
|
||||
onPointerLeave={handlePointerLeave}
|
||||
onPointerMove={handlePointerMove}
|
||||
>
|
||||
<defs>{getShapeUtils(shape).render(shape)}</defs>
|
||||
<HoverIndicator as="use" xlinkHref={"#" + id} />
|
||||
|
@ -86,13 +99,12 @@ const StyledGroup = styled("g", {
|
|||
[`& ${Indicator}`]: {
|
||||
stroke: "$selected",
|
||||
},
|
||||
[`&:hover ${HoverIndicator}`]: {
|
||||
opacity: "1",
|
||||
stroke: "$hint",
|
||||
},
|
||||
},
|
||||
false: {
|
||||
[`&:hover ${HoverIndicator}`]: {
|
||||
false: {},
|
||||
},
|
||||
isHovered: {
|
||||
true: {
|
||||
[`& ${HoverIndicator}`]: {
|
||||
opacity: "1",
|
||||
stroke: "$hint",
|
||||
},
|
||||
|
|
9
components/code-panel/code-as-string.ts
Normal file
9
components/code-panel/code-as-string.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
// This is the code library.
|
||||
|
||||
export default `
|
||||
|
||||
// Hello world
|
||||
const name = "steve"
|
||||
const age = 93
|
||||
|
||||
`
|
101
components/code-panel/code-docs.tsx
Normal file
101
components/code-panel/code-docs.tsx
Normal file
|
@ -0,0 +1,101 @@
|
|||
import styled from "styles"
|
||||
|
||||
export default function CodeDocs({ isHidden }: { isHidden: boolean }) {
|
||||
return (
|
||||
<StyledDocs isHidden={isHidden}>
|
||||
<h2>Docs</h2>
|
||||
</StyledDocs>
|
||||
)
|
||||
}
|
||||
|
||||
const StyledDocs = styled("div", {
|
||||
position: "absolute",
|
||||
backgroundColor: "$panel",
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
padding: 16,
|
||||
font: "$docs",
|
||||
overflowY: "scroll",
|
||||
userSelect: "none",
|
||||
paddingBottom: 64,
|
||||
|
||||
variants: {
|
||||
isHidden: {
|
||||
true: {
|
||||
visibility: "hidden",
|
||||
},
|
||||
false: {
|
||||
visibility: "visible",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"& ol": {},
|
||||
|
||||
"& li": {
|
||||
marginTop: 8,
|
||||
marginBottom: 4,
|
||||
},
|
||||
|
||||
"& code": {
|
||||
font: "$mono",
|
||||
},
|
||||
|
||||
"& hr": {
|
||||
margin: "32px 0",
|
||||
borderColor: "$muted",
|
||||
},
|
||||
|
||||
"& h2": {
|
||||
margin: "24px 0px",
|
||||
},
|
||||
|
||||
"& h3": {
|
||||
fontSize: 20,
|
||||
margin: "48px 0px 32px 0px",
|
||||
},
|
||||
|
||||
"& h3 > code": {
|
||||
fontWeight: 600,
|
||||
font: "$monoheading",
|
||||
},
|
||||
|
||||
"& h4": {
|
||||
margin: "32px 0px 0px 0px",
|
||||
},
|
||||
|
||||
"& h4 > code": {
|
||||
font: "$monoheading",
|
||||
fontSize: 16,
|
||||
userSelect: "all",
|
||||
},
|
||||
|
||||
"& h4 > code > i": {
|
||||
fontSize: 14,
|
||||
color: "$muted",
|
||||
},
|
||||
|
||||
"& pre": {
|
||||
backgroundColor: "$bounds_bg",
|
||||
padding: 16,
|
||||
borderRadius: 4,
|
||||
userSelect: "all",
|
||||
margin: "24px 0",
|
||||
},
|
||||
|
||||
"& p > code, blockquote > code": {
|
||||
backgroundColor: "$bounds_bg",
|
||||
padding: "2px 4px",
|
||||
borderRadius: 2,
|
||||
color: "$code",
|
||||
},
|
||||
|
||||
"& blockquote": {
|
||||
backgroundColor: "rgba(144, 144, 144, .05)",
|
||||
padding: 12,
|
||||
margin: "20px 0",
|
||||
borderRadius: 8,
|
||||
},
|
||||
})
|
214
components/code-panel/code-editor.tsx
Normal file
214
components/code-panel/code-editor.tsx
Normal file
|
@ -0,0 +1,214 @@
|
|||
import Editor, { Monaco } from "@monaco-editor/react"
|
||||
import useTheme from "hooks/useTheme"
|
||||
import prettier from "prettier/standalone"
|
||||
import parserTypeScript from "prettier/parser-typescript"
|
||||
import codeAsString from "./code-as-string"
|
||||
import React, { useCallback, useEffect, useRef } from "react"
|
||||
import styled from "styles"
|
||||
import { IMonaco, IMonacoEditor } from "types"
|
||||
|
||||
interface Props {
|
||||
value: string
|
||||
error: { line: number }
|
||||
fontSize: number
|
||||
monacoRef?: React.MutableRefObject<IMonaco>
|
||||
editorRef?: React.MutableRefObject<IMonacoEditor>
|
||||
readOnly?: boolean
|
||||
onMount?: (value: string, editor: IMonacoEditor) => void
|
||||
onUnmount?: (editor: IMonacoEditor) => void
|
||||
onChange?: (value: string, editor: IMonacoEditor) => void
|
||||
onSave?: (value: string, editor: IMonacoEditor) => void
|
||||
onError?: (error: Error, line: number, col: number) => void
|
||||
onKey?: () => void
|
||||
}
|
||||
|
||||
export default function CodeEditor({
|
||||
editorRef,
|
||||
monacoRef,
|
||||
fontSize,
|
||||
value,
|
||||
error,
|
||||
readOnly,
|
||||
onChange,
|
||||
onSave,
|
||||
onKey,
|
||||
}: Props) {
|
||||
const { theme } = useTheme()
|
||||
const rEditor = useRef<IMonacoEditor>(null)
|
||||
const rMonaco = useRef<IMonaco>(null)
|
||||
|
||||
const handleBeforeMount = useCallback((monaco: Monaco) => {
|
||||
if (monacoRef) {
|
||||
monacoRef.current = monaco
|
||||
}
|
||||
rMonaco.current = monaco
|
||||
|
||||
monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
|
||||
allowJs: true,
|
||||
checkJs: false,
|
||||
strict: false,
|
||||
noLib: true,
|
||||
lib: ["es6"],
|
||||
target: monaco.languages.typescript.ScriptTarget.ES2015,
|
||||
allowNonTsExtensions: true,
|
||||
})
|
||||
|
||||
monaco.languages.typescript.typescriptDefaults.setEagerModelSync(true)
|
||||
|
||||
monaco.languages.typescript.javascriptDefaults.setEagerModelSync(true)
|
||||
|
||||
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
|
||||
noSemanticValidation: true,
|
||||
noSyntaxValidation: true,
|
||||
})
|
||||
|
||||
monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({
|
||||
noSemanticValidation: true,
|
||||
noSyntaxValidation: true,
|
||||
})
|
||||
|
||||
monaco.languages.typescript.javascriptDefaults.addExtraLib(codeAsString)
|
||||
|
||||
monaco.languages.registerDocumentFormattingEditProvider("javascript", {
|
||||
async provideDocumentFormattingEdits(model) {
|
||||
const text = prettier.format(model.getValue(), {
|
||||
parser: "typescript",
|
||||
plugins: [parserTypeScript],
|
||||
singleQuote: true,
|
||||
trailingComma: "es5",
|
||||
semi: false,
|
||||
})
|
||||
|
||||
return [
|
||||
{
|
||||
range: model.getFullModelRange(),
|
||||
text,
|
||||
},
|
||||
]
|
||||
},
|
||||
})
|
||||
}, [])
|
||||
|
||||
const handleMount = useCallback((editor: IMonacoEditor) => {
|
||||
if (editorRef) {
|
||||
editorRef.current = editor
|
||||
}
|
||||
rEditor.current = editor
|
||||
|
||||
editor.updateOptions({
|
||||
fontSize,
|
||||
wordBasedSuggestions: false,
|
||||
minimap: { enabled: false },
|
||||
lightbulb: {
|
||||
enabled: false,
|
||||
},
|
||||
readOnly,
|
||||
})
|
||||
}, [])
|
||||
|
||||
const handleChange = useCallback((code: string | undefined) => {
|
||||
onChange(code, rEditor.current)
|
||||
}, [])
|
||||
|
||||
const handleKeydown = useCallback(
|
||||
(e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
onKey && onKey()
|
||||
e.stopPropagation()
|
||||
const metaKey = navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey
|
||||
if (e.key === "s" && metaKey) {
|
||||
const editor = rEditor.current
|
||||
if (!editor) return
|
||||
editor
|
||||
.getAction("editor.action.formatDocument")
|
||||
.run()
|
||||
.then(() =>
|
||||
onSave(rEditor.current?.getModel().getValue(), rEditor.current)
|
||||
)
|
||||
|
||||
e.preventDefault()
|
||||
}
|
||||
if (e.key === "p" && metaKey) {
|
||||
e.preventDefault()
|
||||
}
|
||||
if (e.key === "d" && metaKey) {
|
||||
e.preventDefault()
|
||||
}
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
const handleKeyUp = useCallback(
|
||||
(e: React.KeyboardEvent<HTMLDivElement>) => e.stopPropagation(),
|
||||
[]
|
||||
)
|
||||
|
||||
const rDecorations = useRef<any>([])
|
||||
|
||||
useEffect(() => {
|
||||
const monaco = rMonaco.current
|
||||
if (!monaco) return
|
||||
const editor = rEditor.current
|
||||
if (!editor) return
|
||||
|
||||
if (!error) {
|
||||
rDecorations.current = editor.deltaDecorations(rDecorations.current, [])
|
||||
return
|
||||
}
|
||||
|
||||
if (!error.line) return
|
||||
|
||||
rDecorations.current = editor.deltaDecorations(rDecorations.current, [
|
||||
{
|
||||
range: new monaco.Range(
|
||||
Number(error.line) - 1,
|
||||
0,
|
||||
Number(error.line) - 1,
|
||||
0
|
||||
),
|
||||
options: {
|
||||
isWholeLine: true,
|
||||
className: "editorLineError",
|
||||
},
|
||||
},
|
||||
])
|
||||
}, [error])
|
||||
|
||||
useEffect(() => {
|
||||
const monaco = rMonaco.current
|
||||
if (!monaco) return
|
||||
monaco.editor.setTheme(theme === "dark" ? "vs-dark" : "light")
|
||||
}, [theme])
|
||||
|
||||
useEffect(() => {
|
||||
const editor = rEditor.current
|
||||
if (!editor) return
|
||||
|
||||
editor.updateOptions({
|
||||
fontSize,
|
||||
})
|
||||
}, [fontSize])
|
||||
|
||||
return (
|
||||
<EditorContainer onKeyDown={handleKeydown} onKeyUp={handleKeyUp}>
|
||||
<Editor
|
||||
height="100%"
|
||||
language="javascript"
|
||||
value={value}
|
||||
theme={theme === "dark" ? "vs-dark" : "light"}
|
||||
beforeMount={handleBeforeMount}
|
||||
onMount={handleMount}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</EditorContainer>
|
||||
)
|
||||
}
|
||||
|
||||
const EditorContainer = styled("div", {
|
||||
height: "100%",
|
||||
pointerEvents: "all",
|
||||
userSelect: "all",
|
||||
|
||||
".editorLineError": {
|
||||
backgroundColor: "$lineError",
|
||||
},
|
||||
})
|
315
components/code-panel/code-panel.tsx
Normal file
315
components/code-panel/code-panel.tsx
Normal file
|
@ -0,0 +1,315 @@
|
|||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
import React, { useEffect, useRef } from "react"
|
||||
import state, { useSelector } from "state"
|
||||
import { motion } from "framer-motion"
|
||||
import { CodeFile } from "types"
|
||||
import { useStateDesigner } from "@state-designer/react"
|
||||
import CodeDocs from "./code-docs"
|
||||
import CodeEditor from "./code-editor"
|
||||
import {
|
||||
X,
|
||||
Code,
|
||||
Info,
|
||||
PlayCircle,
|
||||
ChevronUp,
|
||||
ChevronDown,
|
||||
} from "react-feather"
|
||||
import styled from "styles"
|
||||
|
||||
// import evalCode from "lib/code"
|
||||
|
||||
const getErrorLineAndColumn = (e: any) => {
|
||||
if ("line" in e) {
|
||||
return { line: Number(e.line), column: e.column }
|
||||
}
|
||||
|
||||
const result = e.stack.match(/:([0-9]+):([0-9]+)/)
|
||||
if (result) {
|
||||
return { line: Number(result[1]) - 1, column: result[2] }
|
||||
}
|
||||
}
|
||||
|
||||
export default function CodePanel() {
|
||||
const rContainer = useRef<HTMLDivElement>(null)
|
||||
|
||||
const fileId = "file0"
|
||||
const isReadOnly = useSelector((s) => s.data.isReadOnly)
|
||||
const file = useSelector((s) => s.data.document.code[fileId])
|
||||
const isOpen = true
|
||||
const fontSize = useSelector((s) => s.data.settings.fontSize)
|
||||
|
||||
const local = useStateDesigner({
|
||||
data: {
|
||||
code: file.code,
|
||||
error: null as { message: string; line: number; column: number } | null,
|
||||
},
|
||||
on: {
|
||||
MOUNTED: "setCode",
|
||||
CHANGED_FILE: "loadFile",
|
||||
},
|
||||
initial: "editingCode",
|
||||
states: {
|
||||
editingCode: {
|
||||
on: {
|
||||
RAN_CODE: "runCode",
|
||||
SAVED_CODE: ["runCode", "saveCode"],
|
||||
CHANGED_CODE: [{ secretlyDo: "setCode" }],
|
||||
CLEARED_ERROR: { if: "hasError", do: "clearError" },
|
||||
TOGGLED_DOCS: { to: "viewingDocs" },
|
||||
},
|
||||
},
|
||||
viewingDocs: {
|
||||
on: {
|
||||
TOGGLED_DOCS: { to: "editingCode" },
|
||||
},
|
||||
},
|
||||
},
|
||||
conditions: {
|
||||
hasError(data) {
|
||||
return !!data.error
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
loadFile(data, payload: { file: CodeFile }) {
|
||||
data.code = payload.file.code
|
||||
},
|
||||
setCode(data, payload: { code: string }) {
|
||||
data.code = payload.code
|
||||
},
|
||||
runCode(data) {
|
||||
let error = null
|
||||
|
||||
// try {
|
||||
// const { nodes, globs } = evalCode(data.code)
|
||||
// state.send("GENERATED_ITEMS", { nodes, globs })
|
||||
// } catch (e) {
|
||||
// error = { message: e.message, ...getErrorLineAndColumn(e) }
|
||||
// }
|
||||
|
||||
data.error = error
|
||||
},
|
||||
saveCode(data) {
|
||||
state.send("CHANGED_CODE", { fileId, code: data.code })
|
||||
},
|
||||
clearError(data) {
|
||||
data.error = null
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
local.send("CHANGED_FILE", { file })
|
||||
}, [file])
|
||||
|
||||
useEffect(() => {
|
||||
local.send("MOUNTED", { code: state.data.document.code[fileId].code })
|
||||
return () => {
|
||||
state.send("CHANGED_CODE", { fileId, code: local.data.code })
|
||||
}
|
||||
}, [])
|
||||
|
||||
const { error } = local.data
|
||||
|
||||
return (
|
||||
<PanelContainer
|
||||
data-bp-desktop
|
||||
ref={rContainer}
|
||||
dragMomentum={false}
|
||||
isCollapsed={!isOpen}
|
||||
>
|
||||
{isOpen ? (
|
||||
<Content>
|
||||
<Header>
|
||||
<IconButton onClick={() => state.send("CLOSED_CODE_PANEL")}>
|
||||
<X />
|
||||
</IconButton>
|
||||
<h3>Code</h3>
|
||||
<ButtonsGroup>
|
||||
<FontSizeButtons>
|
||||
<IconButton
|
||||
disabled={!local.isIn("editingCode")}
|
||||
onClick={() => state.send("INCREASED_CODE_FONT_SIZE")}
|
||||
>
|
||||
<ChevronUp />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
disabled={!local.isIn("editingCode")}
|
||||
onClick={() => state.send("DECREASED_CODE_FONT_SIZE")}
|
||||
>
|
||||
<ChevronDown />
|
||||
</IconButton>
|
||||
</FontSizeButtons>
|
||||
<IconButton onClick={() => local.send("TOGGLED_DOCS")}>
|
||||
<Info />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
disabled={!local.isIn("editingCode")}
|
||||
onClick={() => local.send("SAVED_CODE")}
|
||||
>
|
||||
<PlayCircle />
|
||||
</IconButton>
|
||||
</ButtonsGroup>
|
||||
</Header>
|
||||
<EditorContainer>
|
||||
<CodeEditor
|
||||
fontSize={fontSize}
|
||||
readOnly={isReadOnly}
|
||||
value={file.code}
|
||||
error={error}
|
||||
onChange={(code) => local.send("CHANGED_CODE", { code })}
|
||||
onSave={() => local.send("SAVED_CODE")}
|
||||
onKey={() => local.send("CLEARED_ERROR")}
|
||||
/>
|
||||
<CodeDocs isHidden={!local.isIn("viewingDocs")} />
|
||||
</EditorContainer>
|
||||
<ErrorContainer>
|
||||
{error &&
|
||||
(error.line
|
||||
? `(${Number(error.line) - 2}:${error.column}) ${error.message}`
|
||||
: error.message)}
|
||||
</ErrorContainer>
|
||||
</Content>
|
||||
) : (
|
||||
<IconButton onClick={() => state.send("OPENED_CODE_PANEL")}>
|
||||
<Code />
|
||||
</IconButton>
|
||||
)}
|
||||
</PanelContainer>
|
||||
)
|
||||
}
|
||||
|
||||
const PanelContainer = styled(motion.div, {
|
||||
position: "absolute",
|
||||
top: "8px",
|
||||
right: "8px",
|
||||
bottom: "8px",
|
||||
backgroundColor: "$panel",
|
||||
borderRadius: "4px",
|
||||
overflow: "hidden",
|
||||
border: "1px solid $border",
|
||||
pointerEvents: "all",
|
||||
userSelect: "none",
|
||||
zIndex: 200,
|
||||
|
||||
button: {
|
||||
border: "none",
|
||||
},
|
||||
|
||||
variants: {
|
||||
isCollapsed: {
|
||||
true: {},
|
||||
false: {
|
||||
height: "400px",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const IconButton = styled("button", {
|
||||
height: "40px",
|
||||
width: "40px",
|
||||
backgroundColor: "$panel",
|
||||
borderRadius: "4px",
|
||||
border: "1px solid $border",
|
||||
padding: "0",
|
||||
margin: "0",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
outline: "none",
|
||||
pointerEvents: "all",
|
||||
cursor: "pointer",
|
||||
|
||||
"&:hover:not(:disabled)": {
|
||||
backgroundColor: "$panel",
|
||||
},
|
||||
|
||||
"&:disabled": {
|
||||
opacity: "0.5",
|
||||
},
|
||||
|
||||
svg: {
|
||||
height: "20px",
|
||||
width: "20px",
|
||||
strokeWidth: "2px",
|
||||
stroke: "$text",
|
||||
},
|
||||
})
|
||||
|
||||
const Content = styled("div", {
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1fr",
|
||||
gridTemplateRows: "auto 1fr 28px",
|
||||
minWidth: "100%",
|
||||
width: 560,
|
||||
maxWidth: 560,
|
||||
overflow: "hidden",
|
||||
height: "100%",
|
||||
userSelect: "none",
|
||||
pointerEvents: "all",
|
||||
})
|
||||
|
||||
const Header = styled("div", {
|
||||
pointerEvents: "all",
|
||||
display: "grid",
|
||||
gridTemplateColumns: "auto 1fr",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
borderBottom: "1px solid $border",
|
||||
|
||||
"& button": {
|
||||
gridColumn: "1",
|
||||
gridRow: "1",
|
||||
},
|
||||
|
||||
"& h3": {
|
||||
gridColumn: "1 / span 3",
|
||||
gridRow: "1",
|
||||
textAlign: "center",
|
||||
margin: "0",
|
||||
padding: "0",
|
||||
fontSize: "16px",
|
||||
},
|
||||
})
|
||||
|
||||
const ButtonsGroup = styled("div", {
|
||||
gridRow: "1",
|
||||
gridColumn: "3",
|
||||
display: "flex",
|
||||
})
|
||||
|
||||
const EditorContainer = styled("div", {
|
||||
position: "relative",
|
||||
pointerEvents: "all",
|
||||
overflowY: "scroll",
|
||||
})
|
||||
|
||||
const ErrorContainer = styled("div", {
|
||||
overflowX: "scroll",
|
||||
color: "$text",
|
||||
font: "$debug",
|
||||
padding: "0 12px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
})
|
||||
|
||||
const FontSizeButtons = styled("div", {
|
||||
paddingRight: 4,
|
||||
|
||||
"& > button": {
|
||||
height: "50%",
|
||||
width: "100%",
|
||||
|
||||
"&:nth-of-type(1)": {
|
||||
paddingTop: 4,
|
||||
},
|
||||
|
||||
"&:nth-of-type(2)": {
|
||||
paddingBottom: 4,
|
||||
},
|
||||
|
||||
"& svg": {
|
||||
height: 12,
|
||||
},
|
||||
},
|
||||
})
|
47
components/code-panel/example-code.ts
Normal file
47
components/code-panel/example-code.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
export default `// Basic nodes and globs
|
||||
|
||||
const nodeA = new Node({
|
||||
x: -100,
|
||||
y: 0,
|
||||
});
|
||||
|
||||
const nodeB = new Node({
|
||||
x: 100,
|
||||
y: 0,
|
||||
});
|
||||
|
||||
const glob = new Glob({
|
||||
start: nodeA,
|
||||
end: nodeB,
|
||||
D: { x: 0, y: 60 },
|
||||
Dp: { x: 0, y: 90 },
|
||||
});
|
||||
|
||||
// Something more interesting...
|
||||
|
||||
const PI2 = Math.PI * 2,
|
||||
center = { x: 0, y: 0 },
|
||||
radius = 400;
|
||||
|
||||
let prev;
|
||||
|
||||
for (let i = 0; i < 21; i++) {
|
||||
const t = i * (PI2 / 20);
|
||||
|
||||
const node = new Node({
|
||||
x: center.x + radius * Math.sin(t),
|
||||
y: center.y + radius * Math.cos(t),
|
||||
});
|
||||
|
||||
if (prev !== undefined) {
|
||||
new Glob({
|
||||
start: prev,
|
||||
end: node,
|
||||
D: center,
|
||||
Dp: center,
|
||||
});
|
||||
}
|
||||
|
||||
prev = node;
|
||||
}
|
||||
`
|
|
@ -1,6 +1,7 @@
|
|||
import useKeyboardEvents from "hooks/useKeyboardEvents"
|
||||
import Canvas from "./canvas/canvas"
|
||||
import StatusBar from "./status-bar"
|
||||
import CodePanel from "./code-panel/code-panel"
|
||||
|
||||
export default function Editor() {
|
||||
useKeyboardEvents()
|
||||
|
@ -9,6 +10,7 @@ export default function Editor() {
|
|||
<>
|
||||
<Canvas />
|
||||
<StatusBar />
|
||||
{/* <CodePanel /> */}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
12
hooks/useTheme.ts
Normal file
12
hooks/useTheme.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { useCallback } from "react"
|
||||
import state, { useSelector } from "state"
|
||||
|
||||
export default function useTheme() {
|
||||
const theme = useSelector((state) =>
|
||||
state.data.settings.darkMode ? "dark" : "light"
|
||||
)
|
||||
|
||||
const toggleTheme = useCallback(() => state.send("TOGGLED_THEME"), [])
|
||||
|
||||
return { theme, toggleTheme }
|
||||
}
|
24
lib/code/circle.ts
Normal file
24
lib/code/circle.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
import CodeShape from "./index"
|
||||
import { v4 as uuid } from "uuid"
|
||||
import { CircleShape, ShapeType } from "types"
|
||||
|
||||
export default class Circle extends CodeShape<CircleShape> {
|
||||
constructor(props = {} as Partial<CircleShape>) {
|
||||
super({
|
||||
id: uuid(),
|
||||
type: ShapeType.Circle,
|
||||
name: "Circle",
|
||||
parentId: "page0",
|
||||
childIndex: 0,
|
||||
point: [0, 0],
|
||||
rotation: 0,
|
||||
radius: 20,
|
||||
style: {},
|
||||
...props,
|
||||
})
|
||||
}
|
||||
|
||||
get radius() {
|
||||
return this.shape.radius
|
||||
}
|
||||
}
|
54
lib/code/index.ts
Normal file
54
lib/code/index.ts
Normal file
|
@ -0,0 +1,54 @@
|
|||
import { Shape } from "types"
|
||||
import * as vec from "utils/vec"
|
||||
import { getShapeUtils } from "lib/shapes"
|
||||
|
||||
export default class CodeShape<T extends Shape> {
|
||||
private _shape: T
|
||||
|
||||
constructor(props: T) {
|
||||
this._shape = props
|
||||
shapeMap.add(this)
|
||||
}
|
||||
|
||||
destroy() {
|
||||
shapeMap.delete(this)
|
||||
}
|
||||
|
||||
moveTo(point: number[]) {
|
||||
this.shape.point = point
|
||||
}
|
||||
|
||||
translate(delta: number[]) {
|
||||
this.shape.point = vec.add(this._shape.point, delta)
|
||||
}
|
||||
|
||||
rotate(rotation: number) {
|
||||
this.shape.rotation = rotation
|
||||
}
|
||||
|
||||
scale(scale: number) {
|
||||
return getShapeUtils(this.shape).scale(this.shape, scale)
|
||||
}
|
||||
|
||||
getBounds() {
|
||||
return getShapeUtils(this.shape).getBounds(this.shape)
|
||||
}
|
||||
|
||||
hitTest(point: number[]) {
|
||||
return getShapeUtils(this.shape).hitTest(this.shape, point)
|
||||
}
|
||||
|
||||
get shape() {
|
||||
return this._shape
|
||||
}
|
||||
|
||||
get point() {
|
||||
return [...this.shape.point]
|
||||
}
|
||||
|
||||
get rotation() {
|
||||
return this.shape.rotation
|
||||
}
|
||||
}
|
||||
|
||||
export const shapeMap = new Set<CodeShape<Shape>>([])
|
24
lib/code/rectangle.ts
Normal file
24
lib/code/rectangle.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
import CodeShape from "./index"
|
||||
import { v4 as uuid } from "uuid"
|
||||
import { RectangleShape, ShapeType } from "types"
|
||||
|
||||
export default class Rectangle extends CodeShape<RectangleShape> {
|
||||
constructor(props = {} as Partial<RectangleShape>) {
|
||||
super({
|
||||
id: uuid(),
|
||||
type: ShapeType.Rectangle,
|
||||
name: "Rectangle",
|
||||
parentId: "page0",
|
||||
childIndex: 0,
|
||||
point: [0, 0],
|
||||
size: [1, 1],
|
||||
rotation: 0,
|
||||
style: {},
|
||||
...props,
|
||||
})
|
||||
}
|
||||
|
||||
get size() {
|
||||
return this.shape.size
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ import { CircleShape, ShapeType } from "types"
|
|||
import { createShape } from "./index"
|
||||
import { boundsContained } from "utils/bounds"
|
||||
import { intersectCircleBounds } from "utils/intersections"
|
||||
import { pointInCircle } from "utils/hitTests"
|
||||
|
||||
const circle = createShape<CircleShape>({
|
||||
boundsCache: new WeakMap([]),
|
||||
|
@ -16,8 +17,8 @@ const circle = createShape<CircleShape>({
|
|||
parentId: "page0",
|
||||
childIndex: 0,
|
||||
point: [0, 0],
|
||||
radius: 20,
|
||||
rotation: 0,
|
||||
radius: 20,
|
||||
style: {},
|
||||
...props,
|
||||
}
|
||||
|
@ -51,9 +52,11 @@ const circle = createShape<CircleShape>({
|
|||
return bounds
|
||||
},
|
||||
|
||||
hitTest(shape, test) {
|
||||
return (
|
||||
vec.dist(vec.addScalar(shape.point, shape.radius), test) < shape.radius
|
||||
hitTest(shape, point) {
|
||||
return pointInCircle(
|
||||
point,
|
||||
vec.addScalar(shape.point, shape.radius),
|
||||
shape.radius
|
||||
)
|
||||
},
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ const dot = createShape<DotShape>({
|
|||
},
|
||||
|
||||
hitTest(shape, test) {
|
||||
return vec.dist(shape.point, test) < 4
|
||||
return true
|
||||
},
|
||||
|
||||
hitTestBounds(this, shape, brushBounds) {
|
||||
|
|
|
@ -57,7 +57,12 @@ const ellipse = createShape<EllipseShape>({
|
|||
},
|
||||
|
||||
hitTest(shape, point) {
|
||||
return pointInEllipse(point, shape.point, shape.radiusX, shape.radiusY)
|
||||
return pointInEllipse(
|
||||
point,
|
||||
vec.add(shape.point, [shape.radiusX, shape.radiusY]),
|
||||
shape.radiusX,
|
||||
shape.radiusY
|
||||
)
|
||||
},
|
||||
|
||||
hitTestBounds(this, shape, brushBounds) {
|
||||
|
|
|
@ -59,7 +59,7 @@ const line = createShape<LineShape>({
|
|||
},
|
||||
|
||||
hitTest(shape, test) {
|
||||
return vec.dist(shape.point, test) < 4
|
||||
return true
|
||||
},
|
||||
|
||||
hitTestBounds(this, shape, brushBounds) {
|
||||
|
|
|
@ -57,8 +57,19 @@ const polyline = createShape<PolylineShape>({
|
|||
return bounds
|
||||
},
|
||||
|
||||
hitTest(shape) {
|
||||
return true
|
||||
hitTest(shape, point) {
|
||||
let pt = vec.sub(point, shape.point)
|
||||
let prev = shape.points[0]
|
||||
|
||||
for (let i = 1; i < shape.points.length; i++) {
|
||||
let curr = shape.points[i]
|
||||
if (vec.distanceToLineSegment(prev, curr, pt) < 4) {
|
||||
return true
|
||||
}
|
||||
prev = curr
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
|
||||
hitTestBounds(this, shape, bounds) {
|
||||
|
|
|
@ -8,20 +8,24 @@
|
|||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@monaco-editor/react": "^4.1.3",
|
||||
"@state-designer/react": "^1.7.1",
|
||||
"@stitches/react": "^0.1.9",
|
||||
"@types/uuid": "^8.3.0",
|
||||
"framer-motion": "^4.1.16",
|
||||
"next": "10.2.0",
|
||||
"perfect-freehand": "^0.4.7",
|
||||
"prettier": "^2.3.0",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"react-feather": "^2.0.9",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/next": "^9.0.0",
|
||||
"@types/react": "^17.0.5",
|
||||
"@types/react-dom": "^17.0.3",
|
||||
"monaco-editor": "^0.24.0",
|
||||
"typescript": "^4.2.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,4 +90,11 @@ export const defaultDocument: Data["document"] = {
|
|||
},
|
||||
},
|
||||
},
|
||||
code: {
|
||||
file0: {
|
||||
id: "file0",
|
||||
name: "index.ts",
|
||||
code: "// Hello world",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -23,6 +23,23 @@ class Inputs {
|
|||
return info
|
||||
}
|
||||
|
||||
pointerEnter(e: PointerEvent | React.PointerEvent, target: string) {
|
||||
const { shiftKey, ctrlKey, metaKey, altKey } = e
|
||||
|
||||
const info = {
|
||||
target,
|
||||
pointerId: e.pointerId,
|
||||
origin: [e.clientX, e.clientY],
|
||||
point: [e.clientX, e.clientY],
|
||||
shiftKey,
|
||||
ctrlKey,
|
||||
metaKey: isDarwin() ? metaKey : ctrlKey,
|
||||
altKey,
|
||||
}
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
pointerMove(e: PointerEvent | React.PointerEvent) {
|
||||
const { shiftKey, ctrlKey, metaKey, altKey } = e
|
||||
|
||||
|
|
|
@ -9,12 +9,17 @@ import * as Sessions from "./sessions"
|
|||
|
||||
const initialData: Data = {
|
||||
isReadOnly: false,
|
||||
settings: {
|
||||
fontSize: 13,
|
||||
darkMode: false,
|
||||
},
|
||||
camera: {
|
||||
point: [0, 0],
|
||||
zoom: 1,
|
||||
},
|
||||
brush: undefined,
|
||||
pointedId: null,
|
||||
hoveredId: null,
|
||||
selectedIds: new Set([]),
|
||||
currentPageId: "page0",
|
||||
document: defaultDocument,
|
||||
|
@ -45,6 +50,15 @@ const state = createState({
|
|||
POINTED_BOUNDS: { to: "pointingBounds" },
|
||||
POINTED_BOUNDS_EDGE: { to: "transformingSelection" },
|
||||
POINTED_BOUNDS_CORNER: { to: "transformingSelection" },
|
||||
MOVED_OVER_SHAPE: {
|
||||
if: "pointHitsShape",
|
||||
then: {
|
||||
unless: "shapeIsHovered",
|
||||
do: "setHoveredId",
|
||||
},
|
||||
else: { if: "shapeIsHovered", do: "clearHoveredId" },
|
||||
},
|
||||
UNHOVERED_SHAPE: "clearHoveredId",
|
||||
POINTED_SHAPE: [
|
||||
"setPointedId",
|
||||
{
|
||||
|
@ -135,6 +149,22 @@ const state = createState({
|
|||
isPressingShiftKey(data, payload: { shiftKey: boolean }) {
|
||||
return payload.shiftKey
|
||||
},
|
||||
shapeIsHovered(data, payload: { target: string }) {
|
||||
return data.hoveredId === payload.target
|
||||
},
|
||||
pointHitsShape(data, payload: { target: string; point: number[] }) {
|
||||
const shape =
|
||||
data.document.pages[data.currentPageId].shapes[payload.target]
|
||||
|
||||
console.log(
|
||||
getShapeUtils(shape).hitTest(shape, screenToWorld(payload.point, data))
|
||||
)
|
||||
|
||||
return getShapeUtils(shape).hitTest(
|
||||
shape,
|
||||
screenToWorld(payload.point, data)
|
||||
)
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
// History
|
||||
|
@ -199,6 +229,12 @@ const state = createState({
|
|||
},
|
||||
|
||||
// Selection
|
||||
setHoveredId(data, payload: PointerInfo) {
|
||||
data.hoveredId = payload.target
|
||||
},
|
||||
clearHoveredId(data) {
|
||||
data.hoveredId = undefined
|
||||
},
|
||||
setPointedId(data, payload: PointerInfo) {
|
||||
data.pointedId = payload.target
|
||||
},
|
||||
|
|
|
@ -12,6 +12,9 @@ const { styled, global, css, theme, getCssString } = createCss({
|
|||
selected: "rgba(66, 133, 244, 1.000)",
|
||||
bounds: "rgba(65, 132, 244, 1.000)",
|
||||
boundsBg: "rgba(65, 132, 244, 0.100)",
|
||||
border: "#aaa",
|
||||
panel: "#fefefe",
|
||||
text: "#000",
|
||||
},
|
||||
space: {},
|
||||
fontSizes: {
|
||||
|
@ -22,7 +25,7 @@ const { styled, global, css, theme, getCssString } = createCss({
|
|||
4: "18px",
|
||||
},
|
||||
fonts: {
|
||||
ui: `"Recursive", system-ui, sans-serif`,
|
||||
ui: '"Recursive", system-ui, sans-serif',
|
||||
},
|
||||
fontWeights: {},
|
||||
lineHeights: {},
|
||||
|
|
18
types.ts
18
types.ts
|
@ -1,7 +1,13 @@
|
|||
import * as monaco from "monaco-editor/esm/vs/editor/editor.api"
|
||||
|
||||
import React from "react"
|
||||
|
||||
export interface Data {
|
||||
isReadOnly: boolean
|
||||
settings: {
|
||||
fontSize: number
|
||||
darkMode: boolean
|
||||
}
|
||||
camera: {
|
||||
point: number[]
|
||||
zoom: number
|
||||
|
@ -10,11 +16,19 @@ export interface Data {
|
|||
currentPageId: string
|
||||
selectedIds: Set<string>
|
||||
pointedId?: string
|
||||
hoveredId?: string
|
||||
document: {
|
||||
pages: Record<string, Page>
|
||||
code: Record<string, CodeFile>
|
||||
}
|
||||
}
|
||||
|
||||
export interface CodeFile {
|
||||
id: string
|
||||
name: string
|
||||
code: string
|
||||
}
|
||||
|
||||
export interface Page {
|
||||
id: string
|
||||
type: "page"
|
||||
|
@ -172,3 +186,7 @@ export enum TransformCorner {
|
|||
BottomRight = "bottom_right_corner",
|
||||
BottomLeft = "bottom_left_corner",
|
||||
}
|
||||
|
||||
export type IMonaco = typeof monaco
|
||||
|
||||
export type IMonacoEditor = monaco.editor.IStandaloneCodeEditor
|
||||
|
|
38
yarn.lock
38
yarn.lock
|
@ -1155,6 +1155,22 @@
|
|||
"@types/yargs" "^15.0.0"
|
||||
chalk "^3.0.0"
|
||||
|
||||
"@monaco-editor/loader@^1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@monaco-editor/loader/-/loader-1.0.1.tgz#7068c9b07bbc65387c0e7a4df6dac0a326155905"
|
||||
integrity sha512-hycGOhLqLYjnD0A/FHs56covEQWnDFrSnm/qLKkB/yoeayQ7ju+Vaj4SdTojGrXeY6jhMDx59map0+Jqwquh1Q==
|
||||
dependencies:
|
||||
state-local "^1.0.6"
|
||||
|
||||
"@monaco-editor/react@^4.1.3":
|
||||
version "4.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@monaco-editor/react/-/react-4.1.3.tgz#7dcaa584f2a4e8bd8f5298604f0b5368f8ebca55"
|
||||
integrity sha512-kqcjVuoy6btcgALAk4RV/SlasveM+WTw5lzzlyq5FhKXjF8wu5tSe/2oCQ1uhLpcdtxcHfx3L0HrcAPWnejFnQ==
|
||||
dependencies:
|
||||
"@monaco-editor/loader" "^1.0.1"
|
||||
prop-types "^15.7.2"
|
||||
state-local "^1.0.7"
|
||||
|
||||
"@next/env@10.2.0":
|
||||
version "10.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@next/env/-/env-10.2.0.tgz#154dbce2efa3ad067ebd20b7d0aa9aed775e7c97"
|
||||
|
@ -5041,6 +5057,11 @@ mkdirp@0.x, mkdirp@^0.5.1:
|
|||
dependencies:
|
||||
minimist "^1.2.5"
|
||||
|
||||
monaco-editor@^0.24.0:
|
||||
version "0.24.0"
|
||||
resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.24.0.tgz#990b55096bcc95d08d8d28e55264c6eb17707269"
|
||||
integrity sha512-o1f0Lz6ABFNTtnEqqqvlY9qzNx24rQZx1RgYNQ8SkWkE+Ka63keHH/RqxQ4QhN4fs/UYOnvAtEUZsPrzccH++A==
|
||||
|
||||
mri@^1.1.0:
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.6.tgz#49952e1044db21dbf90f6cd92bc9c9a777d415a6"
|
||||
|
@ -5700,6 +5721,11 @@ prettier@^1.19.1:
|
|||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
|
||||
integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==
|
||||
|
||||
prettier@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.0.tgz#b6a5bf1284026ae640f17f7ff5658a7567fc0d18"
|
||||
integrity sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w==
|
||||
|
||||
pretty-format@^25.2.1, pretty-format@^25.5.0:
|
||||
version "25.5.0"
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.5.0.tgz#7873c1d774f682c34b8d48b6743a2bf2ac55791a"
|
||||
|
@ -5846,6 +5872,13 @@ react-dom@17.0.2:
|
|||
object-assign "^4.1.1"
|
||||
scheduler "^0.20.2"
|
||||
|
||||
react-feather@^2.0.9:
|
||||
version "2.0.9"
|
||||
resolved "https://registry.yarnpkg.com/react-feather/-/react-feather-2.0.9.tgz#6e42072130d2fa9a09d4476b0e61b0ed17814480"
|
||||
integrity sha512-yMfCGRkZdXwIs23Zw/zIWCJO3m3tlaUvtHiXlW+3FH7cIT6fiK1iJ7RJWugXq7Fso8ZaQyUm92/GOOHXvkiVUw==
|
||||
dependencies:
|
||||
prop-types "^15.7.2"
|
||||
|
||||
react-is@16.13.1, react-is@^16.12.0, react-is@^16.8.1:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
|
@ -6591,6 +6624,11 @@ stacktrace-parser@0.1.10:
|
|||
dependencies:
|
||||
type-fest "^0.7.1"
|
||||
|
||||
state-local@^1.0.6, state-local@^1.0.7:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/state-local/-/state-local-1.0.7.tgz#da50211d07f05748d53009bee46307a37db386d5"
|
||||
integrity sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==
|
||||
|
||||
static-extend@^0.1.1:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
|
||||
|
|
Loading…
Reference in a new issue