Adds text

This commit is contained in:
Steve Ruiz 2021-06-17 11:43:55 +01:00
parent 098afdd180
commit aabc7e8e0f
17 changed files with 250 additions and 133 deletions

View file

@ -1,6 +1,6 @@
import styled from 'styles' import styled from 'styles'
import state, { useSelector } from 'state' import state, { useSelector } from 'state'
import React, { useRef } from 'react' import React, { useEffect, useRef } from 'react'
import useZoomEvents from 'hooks/useZoomEvents' import useZoomEvents from 'hooks/useZoomEvents'
import useCamera from 'hooks/useCamera' import useCamera from 'hooks/useCamera'
import Defs from './defs' import Defs from './defs'

View file

@ -1,4 +1,4 @@
import React, { useRef, memo } from 'react' import React, { useRef, memo, useEffect } from 'react'
import state, { useSelector } from 'state' import state, { useSelector } from 'state'
import styled from 'styles' import styled from 'styles'
import { getShapeUtils } from 'lib/shape-utils' import { getShapeUtils } from 'lib/shape-utils'
@ -16,14 +16,21 @@ interface ShapeProps {
} }
function Shape({ id, isSelecting, parentPoint }: 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 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) 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 // This is a problem with deleted shapes. The hooks in this component
// may sometimes run before the hook in the Page component, which means // may sometimes run before the hook in the Page component, which means
// a deleted shape will still be pulled here before the page component // 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 style = getShapeStyle(shape.style)
const shapeUtils = getShapeUtils(shape) const shapeUtils = getShapeUtils(shape)
const { isShy, isParent, isForeignObject } = shapeUtils const { isShy, isParent, isForeignObject } = shapeUtils
const bounds = shapeUtils.getBounds(shape) const bounds = shapeUtils.getBounds(shape)
@ -39,17 +47,13 @@ function Shape({ id, isSelecting, parentPoint }: ShapeProps) {
const rotation = shape.rotation * (180 / Math.PI) const rotation = shape.rotation * (180 / Math.PI)
const transform = ` const transform = `
translate(${vec.neg(parentPoint)}) translate(${vec.neg(parentPoint)})
rotate(${rotation}, ${center}) rotate(${rotation}, ${center})
translate(${shape.point}) translate(${shape.point})
` `
return ( return (
<StyledGroup <StyledGroup ref={rGroup} transform={transform}>
ref={rGroup}
transform={transform}
onBlur={() => state.send('BLURRED_SHAPE', { target: id })}
>
{isSelecting && {isSelecting &&
!isShy && !isShy &&
(isForeignObject ? ( (isForeignObject ? (
@ -73,7 +77,7 @@ function Shape({ id, isSelecting, parentPoint }: ShapeProps) {
{!shape.isHidden && {!shape.isHidden &&
(isForeignObject ? ( (isForeignObject ? (
shapeUtils.render(shape, { isEditing }) shapeUtils.render(shape, { isEditing, ref: rFocusable })
) : ( ) : (
<RealShape <RealShape
isParent={isParent} isParent={isParent}
@ -107,7 +111,6 @@ const RealShape = memo(function RealShape({
id, id,
style, style,
isParent, isParent,
isEditing,
}: RealShapeProps) { }: RealShapeProps) {
return <StyledShape as="use" data-shy={isParent} href={'#' + id} {...style} /> return <StyledShape as="use" data-shy={isParent} href={'#' + id} {...style} />
}) })

View file

@ -105,7 +105,7 @@ export default function ToolsPanel() {
</Tooltip> </Tooltip>
<Tooltip label="Text"> <Tooltip label="Text">
<IconButton <IconButton
name={ShapeType.Arrow} name={ShapeType.Text}
bp={{ '@initial': 'mobile', '@sm': 'small' }} bp={{ '@initial': 'mobile', '@sm': 'small' }}
size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }} size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
onClick={selectTextTool} onClick={selectTextTool}

31
decs.d.ts vendored Normal file
View 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
}
}

View file

@ -180,11 +180,7 @@ export default function useKeyboardEvents() {
break break
} }
case 't': { case 't': {
if (metaKey(e)) { state.send('SELECTED_TEXT_TOOL', getKeyboardEventInfo(e))
state.send('DUPLICATED', getKeyboardEventInfo(e))
} else {
state.send('SELECTED_DOT_TOOL', getKeyboardEventInfo(e))
}
break break
} }
case 'c': { case 'c': {

View file

@ -1,11 +1,16 @@
import { useEffect } from "react" import { useEffect } from 'react'
import state from "state" import state from 'state'
export default function useLoadOnMount() { export default function useLoadOnMount() {
useEffect(() => { useEffect(() => {
state.send("MOUNTED") const fonts = (document as any).fonts
fonts
.load('12px Verveine Regular', 'Fonts are loaded!')
.then(() => state.send('MOUNTED'))
return () => { return () => {
state.send("UNMOUNTED") state.send('UNMOUNTED')
} }
}, []) }, [])
} }

View file

@ -63,10 +63,14 @@ export function getFontSize(size: FontSize) {
return fontSizes[size] return fontSizes[size]
} }
export function getFontStyle(size: FontSize, style: ShapeStyles) { export function getFontStyle(
size: FontSize,
scale: number,
style: ShapeStyles
) {
const fontSize = getFontSize(size) const fontSize = getFontSize(size)
return `${fontSize}px Verveine Regular` return `${fontSize * scale}px Verveine Regular`
} }
export function getShapeStyle( export function getShapeStyle(

View file

@ -32,6 +32,7 @@ import draw from './draw'
import arrow from './arrow' import arrow from './arrow'
import group from './group' import group from './group'
import text from './text' import text from './text'
import React from 'react'
/* /*
Shape Utiliies Shape Utiliies
@ -173,9 +174,14 @@ export interface ShapeUtility<K extends Shape> {
render( render(
this: ShapeUtility<K>, this: ShapeUtility<K>,
shape: K, shape: K,
info: { isEditing: boolean } info: {
isEditing: boolean
ref: React.MutableRefObject<HTMLTextAreaElement>
}
): JSX.Element ): JSX.Element
invalidate(this: ShapeUtility<K>, shape: K): ShapeUtility<K>
// Get the bounds of the a shape. // Get the bounds of the a shape.
getBounds(this: ShapeUtility<K>, shape: K): Bounds 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. // Test whether bounds collide with or contain a shape.
hitTestBounds(this: ShapeUtility<K>, shape: K, bounds: Bounds): boolean 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. // A mapping of shape types to shape utilities.
@ -350,9 +356,14 @@ function getDefaultShapeUtil<T extends Shape>(): ShapeUtility<T> {
return this return this
}, },
getShouldDelete(shape) { shouldDelete(shape) {
return false return false
}, },
invalidate(shape) {
this.boundsCache.delete(shape)
return this
},
} }
} }

View file

@ -19,12 +19,14 @@ mdiv.style.whiteSpace = 'pre'
mdiv.style.width = 'auto' mdiv.style.width = 'auto'
mdiv.style.border = '1px solid red' mdiv.style.border = '1px solid red'
mdiv.style.padding = '4px' mdiv.style.padding = '4px'
mdiv.style.lineHeight = '1'
mdiv.style.margin = '0px' mdiv.style.margin = '0px'
mdiv.style.opacity = '0' mdiv.style.opacity = '0'
mdiv.style.position = 'absolute' mdiv.style.position = 'absolute'
mdiv.style.top = '-500px' mdiv.style.top = '-500px'
mdiv.style.left = '0px' mdiv.style.left = '0px'
mdiv.style.zIndex = '9999' mdiv.style.zIndex = '9999'
mdiv.setAttribute('readonly', 'true')
document.body.appendChild(mdiv) document.body.appendChild(mdiv)
const text = registerShapeUtils<TextShape>({ const text = registerShapeUtils<TextShape>({
@ -50,23 +52,20 @@ const text = registerShapeUtils<TextShape>({
isHidden: false, isHidden: false,
style: defaultStyle, style: defaultStyle,
text: '', text: '',
scale: 1,
size: 'auto', size: 'auto',
fontSize: FontSize.Medium, fontSize: FontSize.Medium,
...props, ...props,
} }
}, },
render(shape, { isEditing }) { render(shape, { isEditing, ref }) {
const { id, text, style } = shape const { id, text, style } = shape
const styles = getShapeStyle(style) 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 bounds = this.getBounds(shape)
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
state.send('EDITED_SHAPE', { change: { text: e.currentTarget.value } })
}
return ( return (
<foreignObject <foreignObject
id={id} id={id}
@ -76,44 +75,63 @@ const text = registerShapeUtils<TextShape>({
height={bounds.height} height={bounds.height}
pointerEvents="none" pointerEvents="none"
> >
<StyledText {isEditing ? (
key={id} <StyledTextArea
style={{ ref={ref}
font, style={{
color: styles.fill, font,
}} color: styles.stroke,
value={text} }}
onChange={handleChange} value={text}
isEditing={isEditing} autoComplete="false"
onFocus={(e) => e.currentTarget.select()} 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> </foreignObject>
) )
}, },
getBounds(shape) { getBounds(shape) {
const [minX, minY] = shape.point if (!this.boundsCache.has(shape)) {
let width: number mdiv.innerHTML = shape.text || ' ' // + '&nbsp;'
let height: number mdiv.style.font = getFontStyle(shape.fontSize, shape.scale, shape.style)
if (shape.size === 'auto') { const [minX, minY] = shape.point
// Calculate a size by rendering text into a div const [width, height] = [mdiv.offsetWidth, mdiv.offsetHeight]
mdiv.innerHTML = shape.text + '&nbsp;'
mdiv.style.font = getFontStyle(shape.fontSize, shape.style) this.boundsCache.set(shape, {
;[width, height] = [mdiv.offsetWidth, mdiv.offsetHeight] minX,
} else { maxX: minX + width,
// Use the shape's explicit size for width and height. minY,
;[width, height] = shape.size maxY: minY + height,
width,
height,
})
} }
return { return this.boundsCache.get(shape)
minX,
maxX: minX + width,
minY,
maxY: minY + height,
width,
height,
}
}, },
hitTest(shape, test) { hitTest(shape, test) {
@ -122,37 +140,25 @@ const text = registerShapeUtils<TextShape>({
transform(shape, bounds, { initialShape, transformOrigin, scaleX, scaleY }) { transform(shape, bounds, { initialShape, transformOrigin, scaleX, scaleY }) {
if (shape.rotation === 0 && !shape.isAspectRatioLocked) { if (shape.rotation === 0 && !shape.isAspectRatioLocked) {
shape.size = [bounds.width, bounds.height]
shape.point = [bounds.minX, bounds.minY] shape.point = [bounds.minX, bounds.minY]
shape.scale = initialShape.scale * Math.abs(scaleX)
} else { } else {
if (initialShape.size === 'auto') return shape.point = [bounds.minX, bounds.minY]
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.rotation = shape.rotation =
(scaleX < 0 && scaleY >= 0) || (scaleY < 0 && scaleX >= 0) (scaleX < 0 && scaleY >= 0) || (scaleY < 0 && scaleX >= 0)
? -initialShape.rotation ? -initialShape.rotation
: initialShape.rotation : initialShape.rotation
shape.scale = initialShape.scale * Math.abs(Math.min(scaleX, scaleY))
} }
return this return this
}, },
transformSingle(shape, bounds) { transformSingle(shape, bounds, { initialShape, scaleX }) {
shape.size = [bounds.width, bounds.height]
shape.point = [bounds.minX, bounds.minY] shape.point = [bounds.minX, bounds.minY]
shape.scale = initialShape.scale * Math.abs(scaleX)
return this return this
}, },
@ -161,14 +167,14 @@ const text = registerShapeUtils<TextShape>({
return this return this
}, },
getShouldDelete(shape) { shouldDelete(shape) {
return shape.text.length === 0 return shape.text.length === 0
}, },
}) })
export default text export default text
const StyledText = styled('textarea', { const StyledText = styled('div', {
width: '100%', width: '100%',
height: '100%', height: '100%',
border: 'none', border: 'none',
@ -180,13 +186,21 @@ const StyledText = styled('textarea', {
outline: 'none', outline: 'none',
backgroundColor: 'transparent', backgroundColor: 'transparent',
overflow: 'hidden', overflow: 'hidden',
pointerEvents: 'none',
variants: { userSelect: 'none',
isEditing: { })
true: {
backgroundColor: '$boundsBg', const StyledTextArea = styled('textarea', {
pointerEvents: 'all', width: '100%',
}, height: '100%',
}, border: 'none',
}, padding: '4px',
whiteSpace: 'pre',
resize: 'none',
minHeight: 1,
minWidth: 1,
outline: 'none',
overflow: 'hidden',
backgroundColor: '$boundsBg',
pointerEvents: 'all',
}) })

View file

@ -24,7 +24,7 @@ export default function handleCommand(
const shape = page.shapes[initialShape.id] const shape = page.shapes[initialShape.id]
if (getShapeUtils(shape).getShouldDelete(shape)) { if (getShapeUtils(shape).shouldDelete(shape)) {
delete page.shapes[initialShape.id] delete page.shapes[initialShape.id]
} }
}, },

View file

@ -15,21 +15,20 @@ export default function transformCommand(
history.execute( history.execute(
data, data,
new Command({ new Command({
name: 'translate_shapes', name: 'transform_shapes',
category: 'canvas', category: 'canvas',
do(data, isInitial) { do(data) {
const { type, shapeBounds } = after const { type, shapeBounds } = after
const { shapes } = getPage(data) const { shapes } = getPage(data)
for (let id in shapeBounds) { for (let id in shapeBounds) {
const { initialShape, initialShapeBounds, transformOrigin } = const { initialShapeBounds: bounds } = after.shapeBounds[id]
shapeBounds[id] const { initialShape, transformOrigin } = before.shapeBounds[id]
const shape = shapes[id] const shape = shapes[id]
getShapeUtils(shape) getShapeUtils(shape)
.transform(shape, initialShapeBounds, { .transform(shape, bounds, {
type, type,
initialShape, initialShape,
transformOrigin, transformOrigin,
@ -42,24 +41,11 @@ export default function transformCommand(
updateParents(data, Object.keys(shapeBounds)) updateParents(data, Object.keys(shapeBounds))
}, },
undo(data) { undo(data) {
const { type, shapeBounds } = before const { shapeBounds } = before
const { shapes } = getPage(data) const { shapes } = getPage(data)
for (let id in shapeBounds) { for (let id in shapeBounds) {
const { initialShape, initialShapeBounds, transformOrigin } = shapes[id] = shapeBounds[id].initialShape
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)
} }
updateParents(data, Object.keys(shapeBounds)) updateParents(data, Object.keys(shapeBounds))

View file

@ -5,6 +5,7 @@ import commands from 'state/commands'
import { current } from 'immer' import { current } from 'immer'
import { import {
getPage, getPage,
getPageState,
getSelectedIds, getSelectedIds,
getSelectedShapes, getSelectedShapes,
getShape, getShape,

View file

@ -127,7 +127,8 @@ export function getTransformSnapshot(data: Data, transformType: Edge | Corner) {
const hasUnlockedShapes = initialShapes.length > 0 const hasUnlockedShapes = initialShapes.length > 0
const isAllAspectRatioLocked = initialShapes.every( const isAllAspectRatioLocked = initialShapes.every(
(shape) => shape.isAspectRatioLocked (shape) =>
shape.isAspectRatioLocked || !getShapeUtils(shape).canChangeAspectRatio
) )
const shapesBounds = Object.fromEntries( const shapesBounds = Object.fromEntries(

View file

@ -25,6 +25,7 @@ import {
getSelectedIds, getSelectedIds,
setSelectedIds, setSelectedIds,
getPageState, getPageState,
getShapes,
} from 'utils/utils' } from 'utils/utils'
import { import {
Data, Data,
@ -117,12 +118,10 @@ const state = createState({
states: { states: {
loading: { loading: {
on: { on: {
MOUNTED: [ MOUNTED: {
'restoreSavedData', do: 'restoreSavedData',
{ to: 'ready',
to: 'ready', },
},
],
}, },
}, },
ready: { ready: {
@ -133,6 +132,7 @@ const state = createState({
else: ['zoomCameraToFit', 'zoomCameraToActual'], else: ['zoomCameraToFit', 'zoomCameraToActual'],
}, },
on: { on: {
LOADED_FONTS: 'resetShapes',
TOGGLED_SHAPE_LOCK: { if: 'hasSelection', do: 'lockSelection' }, TOGGLED_SHAPE_LOCK: { if: 'hasSelection', do: 'lockSelection' },
TOGGLED_SHAPE_HIDE: { if: 'hasSelection', do: 'hideSelection' }, TOGGLED_SHAPE_HIDE: { if: 'hasSelection', do: 'hideSelection' },
TOGGLED_SHAPE_ASPECT_LOCK: { TOGGLED_SHAPE_ASPECT_LOCK: {
@ -206,6 +206,7 @@ const state = createState({
SELECTED_LINE_TOOL: { unless: 'isReadOnly', to: 'line' }, SELECTED_LINE_TOOL: { unless: 'isReadOnly', to: 'line' },
SELECTED_POLYLINE_TOOL: { unless: 'isReadOnly', to: 'polyline' }, SELECTED_POLYLINE_TOOL: { unless: 'isReadOnly', to: 'polyline' },
SELECTED_RECTANGLE_TOOL: { unless: 'isReadOnly', to: 'rectangle' }, SELECTED_RECTANGLE_TOOL: { unless: 'isReadOnly', to: 'rectangle' },
SELECTED_TEXT_TOOL: { unless: 'isReadOnly', to: 'text' },
ZOOMED_CAMERA: { ZOOMED_CAMERA: {
do: 'zoomCamera', do: 'zoomCamera',
}, },
@ -430,8 +431,17 @@ const state = createState({
onExit: 'clearEditingId', onExit: 'clearEditingId',
on: { on: {
EDITED_SHAPE: { do: 'updateEditSession' }, 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: { 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: { ray: {
onEnter: 'setActiveToolRay', onEnter: 'setActiveToolRay',
initial: 'creating', initial: 'creating',
@ -834,12 +874,6 @@ const state = createState({
}, },
}, },
results: { results: {
newArrow() {
return ShapeType.Arrow
},
newDraw() {
return ShapeType.Draw
},
newDot() { newDot() {
return ShapeType.Dot return ShapeType.Dot
}, },
@ -849,6 +883,15 @@ const state = createState({
newLine() { newLine() {
return ShapeType.Line return ShapeType.Line
}, },
newText() {
return ShapeType.Text
},
newDraw() {
return ShapeType.Draw
},
newArrow() {
return ShapeType.Arrow
},
newCircle() { newCircle() {
return ShapeType.Circle return ShapeType.Circle
}, },
@ -861,8 +904,14 @@ const state = createState({
firstSelectedShape(data) { firstSelectedShape(data) {
return getSelectedShapes(data)[0] return getSelectedShapes(data)[0]
}, },
editingShape(data) {
return getShape(data, data.editingId)
},
}, },
conditions: { conditions: {
shouldDeleteShape(data, payload, shape: Shape) {
return getShapeUtils(shape).shouldDelete(shape)
},
isPointingCanvas(data, payload: PointerInfo) { isPointingCanvas(data, payload: PointerInfo) {
return payload.target === 'canvas' return payload.target === 'canvas'
}, },
@ -959,6 +1008,13 @@ const state = createState({
}, },
/* --------------------- Shapes --------------------- */ /* --------------------- Shapes --------------------- */
resetShapes(data) {
const page = getPage(data)
Object.values(page.shapes).forEach((shape) => {
page.shapes[shape.id] = { ...shape }
})
},
createShape(data, payload, type: ShapeType) { createShape(data, payload, type: ShapeType) {
const shape = createShape(type, { const shape = createShape(type, {
parentId: data.currentPageId, parentId: data.currentPageId,
@ -971,6 +1027,8 @@ const state = createState({
? siblings[siblings.length - 1].childIndex + 1 ? siblings[siblings.length - 1].childIndex + 1
: 1 : 1
data.editingId = shape.id
getShapeUtils(shape).setProperty(shape, 'childIndex', childIndex) getShapeUtils(shape).setProperty(shape, 'childIndex', childIndex)
getPage(data).shapes[shape.id] = shape getPage(data).shapes[shape.id] = shape
@ -1344,6 +1402,9 @@ const state = createState({
setActiveToolLine(data) { setActiveToolLine(data) {
data.activeTool = ShapeType.Line data.activeTool = ShapeType.Line
}, },
setActiveToolText(data) {
data.activeTool = ShapeType.Text
},
/* --------------------- Camera --------------------- */ /* --------------------- Camera --------------------- */
@ -1777,3 +1838,6 @@ function getSelectionBounds(data: Data) {
return commonBounds return commonBounds
} }
// state.enableLog(true)
// state.onUpdate((s) => console.log(s.log.filter((l) => l !== 'MOVED_POINTER')))

View file

@ -5,7 +5,7 @@ import state from './state'
import { uniqueId } from 'utils/utils' import { uniqueId } from 'utils/utils'
import * as idb from 'idb-keyval' 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) { function storageId(fileId: string, label: string, id?: string) {
return [CURRENT_VERSION, fileId, label, id].filter(Boolean).join('_') return [CURRENT_VERSION, fileId, label, id].filter(Boolean).join('_')

View file

@ -16,6 +16,6 @@
"rootDir": ".", "rootDir": ".",
"baseUrl": "." "baseUrl": "."
}, },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "decs.d.ts"],
"exclude": ["node_modules"] "exclude": ["node_modules"]
} }

View file

@ -194,6 +194,7 @@ export interface TextShape extends BaseShape {
type: ShapeType.Text type: ShapeType.Text
text: string text: string
size: number[] | 'auto' size: number[] | 'auto'
scale: number
fontSize: FontSize fontSize: FontSize
} }