Adds text
This commit is contained in:
parent
098afdd180
commit
aabc7e8e0f
17 changed files with 250 additions and 133 deletions
|
@ -1,6 +1,6 @@
|
|||
import styled from 'styles'
|
||||
import state, { useSelector } from 'state'
|
||||
import React, { useRef } from 'react'
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import useZoomEvents from 'hooks/useZoomEvents'
|
||||
import useCamera from 'hooks/useCamera'
|
||||
import Defs from './defs'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useRef, memo } from 'react'
|
||||
import React, { useRef, memo, useEffect } from 'react'
|
||||
import state, { useSelector } from 'state'
|
||||
import styled from 'styles'
|
||||
import { getShapeUtils } from 'lib/shape-utils'
|
||||
|
@ -16,14 +16,21 @@ interface ShapeProps {
|
|||
}
|
||||
|
||||
function Shape({ id, isSelecting, parentPoint }: ShapeProps) {
|
||||
const shape = useSelector((s) => getPage(s.data).shapes[id])
|
||||
const rGroup = useRef<SVGGElement>(null)
|
||||
const rFocusable = useRef<HTMLTextAreaElement>(null)
|
||||
|
||||
const isEditing = useSelector((s) => s.data.editingId === id)
|
||||
|
||||
const rGroup = useRef<SVGGElement>(null)
|
||||
const shape = useSelector((s) => getPage(s.data).shapes[id])
|
||||
|
||||
const events = useShapeEvents(id, getShapeUtils(shape)?.isParent, rGroup)
|
||||
|
||||
useEffect(() => {
|
||||
if (isEditing) {
|
||||
setTimeout(() => rFocusable.current?.focus(), 0)
|
||||
}
|
||||
}, [isEditing])
|
||||
|
||||
// This is a problem with deleted shapes. The hooks in this component
|
||||
// may sometimes run before the hook in the Page component, which means
|
||||
// a deleted shape will still be pulled here before the page component
|
||||
|
@ -32,6 +39,7 @@ function Shape({ id, isSelecting, parentPoint }: ShapeProps) {
|
|||
|
||||
const style = getShapeStyle(shape.style)
|
||||
const shapeUtils = getShapeUtils(shape)
|
||||
|
||||
const { isShy, isParent, isForeignObject } = shapeUtils
|
||||
|
||||
const bounds = shapeUtils.getBounds(shape)
|
||||
|
@ -45,11 +53,7 @@ function Shape({ id, isSelecting, parentPoint }: ShapeProps) {
|
|||
`
|
||||
|
||||
return (
|
||||
<StyledGroup
|
||||
ref={rGroup}
|
||||
transform={transform}
|
||||
onBlur={() => state.send('BLURRED_SHAPE', { target: id })}
|
||||
>
|
||||
<StyledGroup ref={rGroup} transform={transform}>
|
||||
{isSelecting &&
|
||||
!isShy &&
|
||||
(isForeignObject ? (
|
||||
|
@ -73,7 +77,7 @@ function Shape({ id, isSelecting, parentPoint }: ShapeProps) {
|
|||
|
||||
{!shape.isHidden &&
|
||||
(isForeignObject ? (
|
||||
shapeUtils.render(shape, { isEditing })
|
||||
shapeUtils.render(shape, { isEditing, ref: rFocusable })
|
||||
) : (
|
||||
<RealShape
|
||||
isParent={isParent}
|
||||
|
@ -107,7 +111,6 @@ const RealShape = memo(function RealShape({
|
|||
id,
|
||||
style,
|
||||
isParent,
|
||||
isEditing,
|
||||
}: RealShapeProps) {
|
||||
return <StyledShape as="use" data-shy={isParent} href={'#' + id} {...style} />
|
||||
})
|
||||
|
|
|
@ -105,7 +105,7 @@ export default function ToolsPanel() {
|
|||
</Tooltip>
|
||||
<Tooltip label="Text">
|
||||
<IconButton
|
||||
name={ShapeType.Arrow}
|
||||
name={ShapeType.Text}
|
||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||
size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
|
||||
onClick={selectTextTool}
|
||||
|
|
31
decs.d.ts
vendored
Normal file
31
decs.d.ts
vendored
Normal file
|
@ -0,0 +1,31 @@
|
|||
type CSSOMString = string
|
||||
type FontFaceLoadStatus = 'unloaded' | 'loading' | 'loaded' | 'error'
|
||||
type FontFaceSetStatus = 'loading' | 'loaded'
|
||||
|
||||
interface FontFace {
|
||||
family: CSSOMString
|
||||
style: CSSOMString
|
||||
weight: CSSOMString
|
||||
stretch: CSSOMString
|
||||
unicodeRange: CSSOMString
|
||||
variant: CSSOMString
|
||||
featureSettings: CSSOMString
|
||||
variationSettings: CSSOMString
|
||||
display: CSSOMString
|
||||
readonly status: FontFaceLoadStatus
|
||||
readonly loaded: Promise<FontFace>
|
||||
load(): Promise<FontFace>
|
||||
}
|
||||
|
||||
interface FontFaceSet {
|
||||
readonly status: FontFaceSetStatus
|
||||
readonly ready: Promise<FontFaceSet>
|
||||
check(font: string, text?: string): Boolean
|
||||
load(font: string, text?: string): Promise<FontFace[]>
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Document {
|
||||
fonts: FontFaceSet
|
||||
}
|
||||
}
|
|
@ -180,11 +180,7 @@ export default function useKeyboardEvents() {
|
|||
break
|
||||
}
|
||||
case 't': {
|
||||
if (metaKey(e)) {
|
||||
state.send('DUPLICATED', getKeyboardEventInfo(e))
|
||||
} else {
|
||||
state.send('SELECTED_DOT_TOOL', getKeyboardEventInfo(e))
|
||||
}
|
||||
state.send('SELECTED_TEXT_TOOL', getKeyboardEventInfo(e))
|
||||
break
|
||||
}
|
||||
case 'c': {
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
import { useEffect } from "react"
|
||||
import state from "state"
|
||||
import { useEffect } from 'react'
|
||||
import state from 'state'
|
||||
|
||||
export default function useLoadOnMount() {
|
||||
useEffect(() => {
|
||||
state.send("MOUNTED")
|
||||
const fonts = (document as any).fonts
|
||||
|
||||
fonts
|
||||
.load('12px Verveine Regular', 'Fonts are loaded!')
|
||||
.then(() => state.send('MOUNTED'))
|
||||
|
||||
return () => {
|
||||
state.send("UNMOUNTED")
|
||||
state.send('UNMOUNTED')
|
||||
}
|
||||
}, [])
|
||||
}
|
||||
|
|
|
@ -63,10 +63,14 @@ export function getFontSize(size: FontSize) {
|
|||
return fontSizes[size]
|
||||
}
|
||||
|
||||
export function getFontStyle(size: FontSize, style: ShapeStyles) {
|
||||
export function getFontStyle(
|
||||
size: FontSize,
|
||||
scale: number,
|
||||
style: ShapeStyles
|
||||
) {
|
||||
const fontSize = getFontSize(size)
|
||||
|
||||
return `${fontSize}px Verveine Regular`
|
||||
return `${fontSize * scale}px Verveine Regular`
|
||||
}
|
||||
|
||||
export function getShapeStyle(
|
||||
|
|
|
@ -32,6 +32,7 @@ import draw from './draw'
|
|||
import arrow from './arrow'
|
||||
import group from './group'
|
||||
import text from './text'
|
||||
import React from 'react'
|
||||
|
||||
/*
|
||||
Shape Utiliies
|
||||
|
@ -173,9 +174,14 @@ export interface ShapeUtility<K extends Shape> {
|
|||
render(
|
||||
this: ShapeUtility<K>,
|
||||
shape: K,
|
||||
info: { isEditing: boolean }
|
||||
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
|
||||
|
||||
|
@ -191,7 +197,7 @@ export interface ShapeUtility<K extends Shape> {
|
|||
// Test whether bounds collide with or contain a shape.
|
||||
hitTestBounds(this: ShapeUtility<K>, shape: K, bounds: Bounds): boolean
|
||||
|
||||
getShouldDelete(this: ShapeUtility<K>, shape: K): boolean
|
||||
shouldDelete(this: ShapeUtility<K>, shape: K): boolean
|
||||
}
|
||||
|
||||
// A mapping of shape types to shape utilities.
|
||||
|
@ -350,9 +356,14 @@ function getDefaultShapeUtil<T extends Shape>(): ShapeUtility<T> {
|
|||
return this
|
||||
},
|
||||
|
||||
getShouldDelete(shape) {
|
||||
shouldDelete(shape) {
|
||||
return false
|
||||
},
|
||||
|
||||
invalidate(shape) {
|
||||
this.boundsCache.delete(shape)
|
||||
return this
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,12 +19,14 @@ mdiv.style.whiteSpace = 'pre'
|
|||
mdiv.style.width = 'auto'
|
||||
mdiv.style.border = '1px solid red'
|
||||
mdiv.style.padding = '4px'
|
||||
mdiv.style.lineHeight = '1'
|
||||
mdiv.style.margin = '0px'
|
||||
mdiv.style.opacity = '0'
|
||||
mdiv.style.position = 'absolute'
|
||||
mdiv.style.top = '-500px'
|
||||
mdiv.style.left = '0px'
|
||||
mdiv.style.zIndex = '9999'
|
||||
mdiv.setAttribute('readonly', 'true')
|
||||
document.body.appendChild(mdiv)
|
||||
|
||||
const text = registerShapeUtils<TextShape>({
|
||||
|
@ -50,23 +52,20 @@ const text = registerShapeUtils<TextShape>({
|
|||
isHidden: false,
|
||||
style: defaultStyle,
|
||||
text: '',
|
||||
scale: 1,
|
||||
size: 'auto',
|
||||
fontSize: FontSize.Medium,
|
||||
...props,
|
||||
}
|
||||
},
|
||||
|
||||
render(shape, { isEditing }) {
|
||||
render(shape, { isEditing, ref }) {
|
||||
const { id, text, style } = shape
|
||||
const styles = getShapeStyle(style)
|
||||
const font = getFontStyle(shape.fontSize, shape.scale, shape.style)
|
||||
|
||||
const font = getFontStyle(shape.fontSize, shape.style)
|
||||
const bounds = this.getBounds(shape)
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
state.send('EDITED_SHAPE', { change: { text: e.currentTarget.value } })
|
||||
}
|
||||
|
||||
return (
|
||||
<foreignObject
|
||||
id={id}
|
||||
|
@ -76,44 +75,63 @@ const text = registerShapeUtils<TextShape>({
|
|||
height={bounds.height}
|
||||
pointerEvents="none"
|
||||
>
|
||||
<StyledText
|
||||
key={id}
|
||||
{isEditing ? (
|
||||
<StyledTextArea
|
||||
ref={ref}
|
||||
style={{
|
||||
font,
|
||||
color: styles.fill,
|
||||
color: styles.stroke,
|
||||
}}
|
||||
value={text}
|
||||
onChange={handleChange}
|
||||
isEditing={isEditing}
|
||||
onFocus={(e) => e.currentTarget.select()}
|
||||
autoComplete="false"
|
||||
autoCapitalize="false"
|
||||
autoCorrect="false"
|
||||
onFocus={(e) => {
|
||||
e.currentTarget.select()
|
||||
state.send('FOCUSED_EDITING_SHAPE')
|
||||
}}
|
||||
onBlur={() => {
|
||||
state.send('BLURRED_EDITING_SHAPE')
|
||||
}}
|
||||
onChange={(e) => {
|
||||
state.send('EDITED_SHAPE', {
|
||||
change: { text: e.currentTarget.value },
|
||||
})
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<StyledText
|
||||
style={{
|
||||
font,
|
||||
color: styles.stroke,
|
||||
}}
|
||||
>
|
||||
{text}
|
||||
</StyledText>
|
||||
)}
|
||||
</foreignObject>
|
||||
)
|
||||
},
|
||||
|
||||
getBounds(shape) {
|
||||
if (!this.boundsCache.has(shape)) {
|
||||
mdiv.innerHTML = shape.text || ' ' // + ' '
|
||||
mdiv.style.font = getFontStyle(shape.fontSize, shape.scale, shape.style)
|
||||
|
||||
const [minX, minY] = shape.point
|
||||
let width: number
|
||||
let height: number
|
||||
const [width, height] = [mdiv.offsetWidth, mdiv.offsetHeight]
|
||||
|
||||
if (shape.size === 'auto') {
|
||||
// Calculate a size by rendering text into a div
|
||||
mdiv.innerHTML = shape.text + ' '
|
||||
mdiv.style.font = getFontStyle(shape.fontSize, shape.style)
|
||||
;[width, height] = [mdiv.offsetWidth, mdiv.offsetHeight]
|
||||
} else {
|
||||
// Use the shape's explicit size for width and height.
|
||||
;[width, height] = shape.size
|
||||
}
|
||||
|
||||
return {
|
||||
this.boundsCache.set(shape, {
|
||||
minX,
|
||||
maxX: minX + width,
|
||||
minY,
|
||||
maxY: minY + height,
|
||||
width,
|
||||
height,
|
||||
})
|
||||
}
|
||||
|
||||
return this.boundsCache.get(shape)
|
||||
},
|
||||
|
||||
hitTest(shape, test) {
|
||||
|
@ -122,37 +140,25 @@ const text = registerShapeUtils<TextShape>({
|
|||
|
||||
transform(shape, bounds, { initialShape, transformOrigin, scaleX, scaleY }) {
|
||||
if (shape.rotation === 0 && !shape.isAspectRatioLocked) {
|
||||
shape.size = [bounds.width, bounds.height]
|
||||
shape.point = [bounds.minX, bounds.minY]
|
||||
shape.scale = initialShape.scale * Math.abs(scaleX)
|
||||
} else {
|
||||
if (initialShape.size === 'auto') return
|
||||
|
||||
shape.size = vec.mul(
|
||||
initialShape.size,
|
||||
Math.min(Math.abs(scaleX), Math.abs(scaleY))
|
||||
)
|
||||
|
||||
shape.point = [
|
||||
bounds.minX +
|
||||
(bounds.width - shape.size[0]) *
|
||||
(scaleX < 0 ? 1 - transformOrigin[0] : transformOrigin[0]),
|
||||
bounds.minY +
|
||||
(bounds.height - shape.size[1]) *
|
||||
(scaleY < 0 ? 1 - transformOrigin[1] : transformOrigin[1]),
|
||||
]
|
||||
shape.point = [bounds.minX, bounds.minY]
|
||||
|
||||
shape.rotation =
|
||||
(scaleX < 0 && scaleY >= 0) || (scaleY < 0 && scaleX >= 0)
|
||||
? -initialShape.rotation
|
||||
: initialShape.rotation
|
||||
|
||||
shape.scale = initialShape.scale * Math.abs(Math.min(scaleX, scaleY))
|
||||
}
|
||||
|
||||
return this
|
||||
},
|
||||
|
||||
transformSingle(shape, bounds) {
|
||||
shape.size = [bounds.width, bounds.height]
|
||||
transformSingle(shape, bounds, { initialShape, scaleX }) {
|
||||
shape.point = [bounds.minX, bounds.minY]
|
||||
shape.scale = initialShape.scale * Math.abs(scaleX)
|
||||
return this
|
||||
},
|
||||
|
||||
|
@ -161,14 +167,14 @@ const text = registerShapeUtils<TextShape>({
|
|||
return this
|
||||
},
|
||||
|
||||
getShouldDelete(shape) {
|
||||
shouldDelete(shape) {
|
||||
return shape.text.length === 0
|
||||
},
|
||||
})
|
||||
|
||||
export default text
|
||||
|
||||
const StyledText = styled('textarea', {
|
||||
const StyledText = styled('div', {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
border: 'none',
|
||||
|
@ -180,13 +186,21 @@ const StyledText = styled('textarea', {
|
|||
outline: 'none',
|
||||
backgroundColor: 'transparent',
|
||||
overflow: 'hidden',
|
||||
pointerEvents: 'none',
|
||||
userSelect: 'none',
|
||||
})
|
||||
|
||||
variants: {
|
||||
isEditing: {
|
||||
true: {
|
||||
const StyledTextArea = styled('textarea', {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
border: 'none',
|
||||
padding: '4px',
|
||||
whiteSpace: 'pre',
|
||||
resize: 'none',
|
||||
minHeight: 1,
|
||||
minWidth: 1,
|
||||
outline: 'none',
|
||||
overflow: 'hidden',
|
||||
backgroundColor: '$boundsBg',
|
||||
pointerEvents: 'all',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
@ -24,7 +24,7 @@ export default function handleCommand(
|
|||
|
||||
const shape = page.shapes[initialShape.id]
|
||||
|
||||
if (getShapeUtils(shape).getShouldDelete(shape)) {
|
||||
if (getShapeUtils(shape).shouldDelete(shape)) {
|
||||
delete page.shapes[initialShape.id]
|
||||
}
|
||||
},
|
||||
|
|
|
@ -15,21 +15,20 @@ export default function transformCommand(
|
|||
history.execute(
|
||||
data,
|
||||
new Command({
|
||||
name: 'translate_shapes',
|
||||
name: 'transform_shapes',
|
||||
category: 'canvas',
|
||||
do(data, isInitial) {
|
||||
do(data) {
|
||||
const { type, shapeBounds } = after
|
||||
|
||||
const { shapes } = getPage(data)
|
||||
|
||||
for (let id in shapeBounds) {
|
||||
const { initialShape, initialShapeBounds, transformOrigin } =
|
||||
shapeBounds[id]
|
||||
|
||||
const { initialShapeBounds: bounds } = after.shapeBounds[id]
|
||||
const { initialShape, transformOrigin } = before.shapeBounds[id]
|
||||
const shape = shapes[id]
|
||||
|
||||
getShapeUtils(shape)
|
||||
.transform(shape, initialShapeBounds, {
|
||||
.transform(shape, bounds, {
|
||||
type,
|
||||
initialShape,
|
||||
transformOrigin,
|
||||
|
@ -42,24 +41,11 @@ export default function transformCommand(
|
|||
updateParents(data, Object.keys(shapeBounds))
|
||||
},
|
||||
undo(data) {
|
||||
const { type, shapeBounds } = before
|
||||
|
||||
const { shapeBounds } = before
|
||||
const { shapes } = getPage(data)
|
||||
|
||||
for (let id in shapeBounds) {
|
||||
const { initialShape, initialShapeBounds, transformOrigin } =
|
||||
shapeBounds[id]
|
||||
const shape = shapes[id]
|
||||
|
||||
getShapeUtils(shape)
|
||||
.transform(shape, initialShapeBounds, {
|
||||
type,
|
||||
initialShape,
|
||||
transformOrigin,
|
||||
scaleX: scaleX < 0 ? scaleX * -1 : scaleX,
|
||||
scaleY: scaleX < 0 ? scaleX * -1 : scaleX,
|
||||
})
|
||||
.onSessionComplete(shape)
|
||||
shapes[id] = shapeBounds[id].initialShape
|
||||
}
|
||||
|
||||
updateParents(data, Object.keys(shapeBounds))
|
||||
|
|
|
@ -5,6 +5,7 @@ import commands from 'state/commands'
|
|||
import { current } from 'immer'
|
||||
import {
|
||||
getPage,
|
||||
getPageState,
|
||||
getSelectedIds,
|
||||
getSelectedShapes,
|
||||
getShape,
|
||||
|
|
|
@ -127,7 +127,8 @@ export function getTransformSnapshot(data: Data, transformType: Edge | Corner) {
|
|||
const hasUnlockedShapes = initialShapes.length > 0
|
||||
|
||||
const isAllAspectRatioLocked = initialShapes.every(
|
||||
(shape) => shape.isAspectRatioLocked
|
||||
(shape) =>
|
||||
shape.isAspectRatioLocked || !getShapeUtils(shape).canChangeAspectRatio
|
||||
)
|
||||
|
||||
const shapesBounds = Object.fromEntries(
|
||||
|
|
|
@ -25,6 +25,7 @@ import {
|
|||
getSelectedIds,
|
||||
setSelectedIds,
|
||||
getPageState,
|
||||
getShapes,
|
||||
} from 'utils/utils'
|
||||
import {
|
||||
Data,
|
||||
|
@ -117,12 +118,10 @@ const state = createState({
|
|||
states: {
|
||||
loading: {
|
||||
on: {
|
||||
MOUNTED: [
|
||||
'restoreSavedData',
|
||||
{
|
||||
MOUNTED: {
|
||||
do: 'restoreSavedData',
|
||||
to: 'ready',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
ready: {
|
||||
|
@ -133,6 +132,7 @@ const state = createState({
|
|||
else: ['zoomCameraToFit', 'zoomCameraToActual'],
|
||||
},
|
||||
on: {
|
||||
LOADED_FONTS: 'resetShapes',
|
||||
TOGGLED_SHAPE_LOCK: { if: 'hasSelection', do: 'lockSelection' },
|
||||
TOGGLED_SHAPE_HIDE: { if: 'hasSelection', do: 'hideSelection' },
|
||||
TOGGLED_SHAPE_ASPECT_LOCK: {
|
||||
|
@ -206,6 +206,7 @@ const state = createState({
|
|||
SELECTED_LINE_TOOL: { unless: 'isReadOnly', to: 'line' },
|
||||
SELECTED_POLYLINE_TOOL: { unless: 'isReadOnly', to: 'polyline' },
|
||||
SELECTED_RECTANGLE_TOOL: { unless: 'isReadOnly', to: 'rectangle' },
|
||||
SELECTED_TEXT_TOOL: { unless: 'isReadOnly', to: 'text' },
|
||||
ZOOMED_CAMERA: {
|
||||
do: 'zoomCamera',
|
||||
},
|
||||
|
@ -430,8 +431,17 @@ const state = createState({
|
|||
onExit: 'clearEditingId',
|
||||
on: {
|
||||
EDITED_SHAPE: { do: 'updateEditSession' },
|
||||
BLURRED_SHAPE: { do: 'completeSession', to: 'selecting' },
|
||||
CANCELLED: { do: 'cancelSession', to: 'selecting' },
|
||||
|
||||
BLURRED_EDITING_SHAPE: { do: 'completeSession', to: 'selecting' },
|
||||
CANCELLED: [
|
||||
{
|
||||
get: 'editingShape',
|
||||
if: 'shouldDeleteShape',
|
||||
do: 'breakSession',
|
||||
else: 'cancelSession',
|
||||
},
|
||||
{ to: 'selecting' },
|
||||
],
|
||||
},
|
||||
},
|
||||
pinching: {
|
||||
|
@ -730,6 +740,36 @@ const state = createState({
|
|||
},
|
||||
},
|
||||
},
|
||||
text: {
|
||||
onEnter: 'setActiveToolText',
|
||||
initial: 'creating',
|
||||
states: {
|
||||
creating: {
|
||||
on: {
|
||||
CANCELLED: { to: 'selecting' },
|
||||
POINTED_SHAPE: [
|
||||
{
|
||||
get: 'newText',
|
||||
do: 'createShape',
|
||||
},
|
||||
{
|
||||
get: 'firstSelectedShape',
|
||||
if: 'canEditSelectedShape',
|
||||
do: 'setEditingId',
|
||||
to: 'editingShape',
|
||||
},
|
||||
],
|
||||
POINTED_CANVAS: [
|
||||
{
|
||||
get: 'newText',
|
||||
do: 'createShape',
|
||||
to: 'editingShape',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ray: {
|
||||
onEnter: 'setActiveToolRay',
|
||||
initial: 'creating',
|
||||
|
@ -834,12 +874,6 @@ const state = createState({
|
|||
},
|
||||
},
|
||||
results: {
|
||||
newArrow() {
|
||||
return ShapeType.Arrow
|
||||
},
|
||||
newDraw() {
|
||||
return ShapeType.Draw
|
||||
},
|
||||
newDot() {
|
||||
return ShapeType.Dot
|
||||
},
|
||||
|
@ -849,6 +883,15 @@ const state = createState({
|
|||
newLine() {
|
||||
return ShapeType.Line
|
||||
},
|
||||
newText() {
|
||||
return ShapeType.Text
|
||||
},
|
||||
newDraw() {
|
||||
return ShapeType.Draw
|
||||
},
|
||||
newArrow() {
|
||||
return ShapeType.Arrow
|
||||
},
|
||||
newCircle() {
|
||||
return ShapeType.Circle
|
||||
},
|
||||
|
@ -861,8 +904,14 @@ const state = createState({
|
|||
firstSelectedShape(data) {
|
||||
return getSelectedShapes(data)[0]
|
||||
},
|
||||
editingShape(data) {
|
||||
return getShape(data, data.editingId)
|
||||
},
|
||||
},
|
||||
conditions: {
|
||||
shouldDeleteShape(data, payload, shape: Shape) {
|
||||
return getShapeUtils(shape).shouldDelete(shape)
|
||||
},
|
||||
isPointingCanvas(data, payload: PointerInfo) {
|
||||
return payload.target === 'canvas'
|
||||
},
|
||||
|
@ -959,6 +1008,13 @@ const state = createState({
|
|||
},
|
||||
|
||||
/* --------------------- Shapes --------------------- */
|
||||
resetShapes(data) {
|
||||
const page = getPage(data)
|
||||
Object.values(page.shapes).forEach((shape) => {
|
||||
page.shapes[shape.id] = { ...shape }
|
||||
})
|
||||
},
|
||||
|
||||
createShape(data, payload, type: ShapeType) {
|
||||
const shape = createShape(type, {
|
||||
parentId: data.currentPageId,
|
||||
|
@ -971,6 +1027,8 @@ const state = createState({
|
|||
? siblings[siblings.length - 1].childIndex + 1
|
||||
: 1
|
||||
|
||||
data.editingId = shape.id
|
||||
|
||||
getShapeUtils(shape).setProperty(shape, 'childIndex', childIndex)
|
||||
|
||||
getPage(data).shapes[shape.id] = shape
|
||||
|
@ -1344,6 +1402,9 @@ const state = createState({
|
|||
setActiveToolLine(data) {
|
||||
data.activeTool = ShapeType.Line
|
||||
},
|
||||
setActiveToolText(data) {
|
||||
data.activeTool = ShapeType.Text
|
||||
},
|
||||
|
||||
/* --------------------- Camera --------------------- */
|
||||
|
||||
|
@ -1777,3 +1838,6 @@ function getSelectionBounds(data: Data) {
|
|||
|
||||
return commonBounds
|
||||
}
|
||||
|
||||
// state.enableLog(true)
|
||||
// state.onUpdate((s) => console.log(s.log.filter((l) => l !== 'MOVED_POINTER')))
|
||||
|
|
|
@ -5,7 +5,7 @@ import state from './state'
|
|||
import { uniqueId } from 'utils/utils'
|
||||
import * as idb from 'idb-keyval'
|
||||
|
||||
const CURRENT_VERSION = 'code_slate_0.0.7'
|
||||
const CURRENT_VERSION = 'code_slate_0.0.8'
|
||||
|
||||
function storageId(fileId: string, label: string, id?: string) {
|
||||
return [CURRENT_VERSION, fileId, label, id].filter(Boolean).join('_')
|
||||
|
|
|
@ -16,6 +16,6 @@
|
|||
"rootDir": ".",
|
||||
"baseUrl": "."
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "decs.d.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
|
1
types.ts
1
types.ts
|
@ -194,6 +194,7 @@ export interface TextShape extends BaseShape {
|
|||
type: ShapeType.Text
|
||||
text: string
|
||||
size: number[] | 'auto'
|
||||
scale: number
|
||||
fontSize: FontSize
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue