Adds text again

This commit is contained in:
Steve Ruiz 2021-08-30 21:17:04 +01:00
parent c28440b878
commit 8fe83ef6c3
18 changed files with 207 additions and 163 deletions

View file

@ -21,6 +21,10 @@ esbuild
minify: false, minify: false,
sourcemap: true, sourcemap: true,
incremental: isDevServer, incremental: isDevServer,
assetNames: 'assets/[name]-[hash]',
loader: {
'.woff': 'file',
},
target: ['chrome58', 'firefox57', 'safari11', 'edge18'], target: ['chrome58', 'firefox57', 'safari11', 'edge18'],
define: { define: {
'process.env.NODE_ENV': isDevServer ? '"development"' : '"production"', 'process.env.NODE_ENV': isDevServer ? '"development"' : '"production"',

View file

@ -34,4 +34,4 @@
"typescript": "4.2.3" "typescript": "4.2.3"
}, },
"gitHead": "a7dac0f83ad998e205c2aab58182cb4ba4e099a6" "gitHead": "a7dac0f83ad998e205c2aab58182cb4ba4e099a6"
} }

Binary file not shown.

View file

@ -1,110 +1,6 @@
import * as React from 'react' import * as React from 'react'
import { import { TLDraw } from '@tldraw/tldraw'
TLDraw,
TLDrawDocument,
ColorStyle,
DashStyle,
SizeStyle,
TLDrawShapeType,
TLDrawState,
TLDrawPatch,
} from '@tldraw/tldraw'
import { usePersistence } from '../hooks/usePersistence'
const initialDoc: TLDrawDocument = {
id: 'doc',
pages: {
page1: {
id: 'page1',
shapes: {
rect1: {
id: 'rect1',
parentId: 'page1',
name: 'Rectangle',
childIndex: 1,
type: TLDrawShapeType.Rectangle,
point: [32, 32],
size: [100, 100],
style: {
dash: DashStyle.Draw,
size: SizeStyle.Medium,
color: ColorStyle.Blue,
},
},
ellipse1: {
id: 'ellipse1',
parentId: 'page1',
name: 'Ellipse',
childIndex: 2,
type: TLDrawShapeType.Ellipse,
point: [132, 132],
radius: [50, 50],
style: {
dash: DashStyle.Draw,
size: SizeStyle.Medium,
color: ColorStyle.Cyan,
},
},
draw1: {
id: 'draw1',
parentId: 'page1',
name: 'Draw',
childIndex: 3,
type: TLDrawShapeType.Draw,
point: [232, 232],
points: [
[50, 0],
[100, 100],
[0, 100],
[50, 0],
[100, 100],
[0, 100],
[50, 0],
[56, 5],
],
style: {
dash: DashStyle.Draw,
size: SizeStyle.Medium,
color: ColorStyle.Green,
},
},
},
bindings: {},
},
},
pageStates: {
page1: {
id: 'page1',
selectedIds: [],
currentParentId: 'page1',
camera: {
point: [0, 0],
zoom: 1,
},
},
},
}
export default function Editor(): JSX.Element { export default function Editor(): JSX.Element {
// const { value, setValue, status } = usePersistence('doc', initialDoc)
// const handleChange = React.useCallback(
// (tlstate: TLDrawState, patch: TLDrawPatch, reason: string) => {
// if (reason.startsWith('session')) {
// return
// }
// setValue(tlstate.document)
// },
// [setValue]
// )
// if (status === 'loading' || value === null) {
// return <div />
// }
// return <TLDraw document={value} onChange={handleChange} />
// Will automatically persist data under the provided id, too
return <TLDraw id="tldraw" /> return <TLDraw id="tldraw" />
} }

View file

@ -9,6 +9,14 @@ async function main() {
fs.mkdirSync('./dist') fs.mkdirSync('./dist')
} }
fs.copyFileSync('package.json', 'dist/package.json', (err) => {
if (err) throw err
})
fs.copyFileSync('README.md', 'dist/README.md', (err) => {
if (err) throw err
})
try { try {
esbuild.buildSync({ esbuild.buildSync({
entryPoints: ['./src/index.ts'], entryPoints: ['./src/index.ts'],
@ -36,13 +44,6 @@ async function main() {
external: ['react', 'react-dom'], external: ['react', 'react-dom'],
}) })
fs.copyFile('package.json', 'dist/package.json', (err) => {
if (err) throw err
})
fs.copyFile('README.md', 'dist/README.md', (err) => {
if (err) throw err
})
console.log(`${name}: Built package.`) console.log(`${name}: Built package.`)
} catch (e) { } catch (e) {
console.log(`× ${name}: Build failed due to an error.`) console.log(`× ${name}: Build failed due to an error.`)

View file

@ -9,8 +9,8 @@ async function main() {
outdir: 'dist/cjs', outdir: 'dist/cjs',
minify: false, minify: false,
bundle: true, bundle: true,
format: 'esm', format: 'cjs',
target: 'cjs', target: 'es6',
jsxFactory: 'React.createElement', jsxFactory: 'React.createElement',
jsxFragment: 'React.Fragment', jsxFragment: 'React.Fragment',
tsconfig: './tsconfig.build.json', tsconfig: './tsconfig.build.json',

View file

@ -1,5 +1,6 @@
/* eslint-disable */ /* eslint-disable */
const fs = require('fs') const fs = require('fs')
const path = require('path')
const esbuild = require('esbuild') const esbuild = require('esbuild')
async function main() { async function main() {
@ -21,6 +22,12 @@ async function main() {
tsconfig: './tsconfig.build.json', tsconfig: './tsconfig.build.json',
external: ['react', 'react-dom'], external: ['react', 'react-dom'],
}) })
var files = fs.readdirSync('src/assets')
for (var i = 0; i < files.length; i++) {
fs.copyFileSync(path.join('src/assets', files[i]), path.join('dist', files[i]))
}
} }
main() main()

Binary file not shown.

View file

@ -4,7 +4,7 @@ import { Renderer } from '@tldraw/core'
import styled from '~styles' import styled from '~styles'
import type { Data, TLDrawDocument } from '~types' import type { Data, TLDrawDocument } from '~types'
import { TLDrawState } from '~state' import { TLDrawState } from '~state'
import { useKeyboardShortcuts, TLDrawContext } from '~hooks' import { useKeyboardShortcuts, TLDrawContext, useCustomFonts } from '~hooks'
import { tldrawShapeUtils } from '~shape' import { tldrawShapeUtils } from '~shape'
import { ContextMenu } from '~components/context-menu' import { ContextMenu } from '~components/context-menu'
import { StylePanel } from '~components/style-panel' import { StylePanel } from '~components/style-panel'
@ -63,6 +63,8 @@ export function TLDraw({ id, document, currentPageId, onMount, onChange: _onChan
useKeyboardShortcuts(tlstate) useKeyboardShortcuts(tlstate)
useCustomFonts()
const page = context.useSelector(pageSelector) const page = context.useSelector(pageSelector)
const pageState = context.useSelector(pageStateSelector) const pageState = context.useSelector(pageStateSelector)

View file

@ -1,3 +1,4 @@
export * from './useKeyboardShortcuts' export * from './useKeyboardShortcuts'
export * from './useTLDrawContext' export * from './useTLDrawContext'
export * from './useTheme' export * from './useTheme'
export * from './useCustomFonts'

View file

@ -0,0 +1,18 @@
import { useStyle, css } from '~hooks/useStyle'
// const customFonts = css`
// @font-face {
// font-family: 'Verveine Regular';
// font-style: normal;
// font-weight: normal;
// src: local('Verveine Regular'), url('/VerveineRegular.woff') format('woff');
// }
// `
const customFonts = css`
@import url('https://fonts.googleapis.com/css2?family=Caveat+Brush&display=swap');
`
export function useCustomFonts() {
useStyle('tldraw-fonts', customFonts)
}

View file

@ -0,0 +1,30 @@
import * as React from 'react'
const styles = new Map<string, HTMLStyleElement>()
export const css = (strings: TemplateStringsArray, ...args: unknown[]) =>
strings.reduce(
(acc, string, index) => acc + string + (index < args.length ? args[index] : ''),
''
)
export function useStyle(uid: string, rules: string) {
React.useLayoutEffect(() => {
if (styles.get(uid)) {
return () => void null
}
const style = document.createElement('style')
style.innerHTML = rules
style.setAttribute('id', uid)
document.head.appendChild(style)
styles.set(uid, style)
return () => {
if (style && document.head.contains(style)) {
document.head.removeChild(style)
styles.delete(uid)
}
}
}, [uid, rules])
}

View file

@ -69,7 +69,7 @@ export function getFontStyle(style: ShapeStyles): string {
const fontSize = getFontSize(style.size) const fontSize = getFontSize(style.size)
const { scale = 1 } = style const { scale = 1 } = style
return `${fontSize * scale}px/1.4 Verveine Regular` return `${fontSize * scale}px/1.3 "Caveat Brush"`
} }
export function getShapeStyle( export function getShapeStyle(

View file

@ -16,16 +16,16 @@ function normalizeText(text: string) {
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
let mdiv: any let melm: any
function getMeasurementDiv() { function getMeasurementDiv() {
// A div used for measurement // A div used for measurement
document.getElementById('__textMeasure')?.remove() document.getElementById('__textMeasure')?.remove()
const mdiv = document.createElement('pre') const pre = document.createElement('pre')
mdiv.id = '__textMeasure' pre.id = '__textMeasure'
Object.assign(mdiv.style, { Object.assign(pre.style, {
whiteSpace: 'pre', whiteSpace: 'pre',
width: 'auto', width: 'auto',
border: '1px solid red', border: '1px solid red',
@ -42,14 +42,14 @@ function getMeasurementDiv() {
dominantBaseline: 'mathematical', dominantBaseline: 'mathematical',
}) })
mdiv.tabIndex = -1 pre.tabIndex = -1
document.body.appendChild(mdiv) document.body.appendChild(pre)
return mdiv return pre
} }
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
getMeasurementDiv() melm = getMeasurementDiv()
} }
export class Text extends TLDrawShapeUtil<TextShape> { export class Text extends TLDrawShapeUtil<TextShape> {
@ -69,10 +69,17 @@ export class Text extends TLDrawShapeUtil<TextShape> {
childIndex: 1, childIndex: 1,
point: [0, 0], point: [0, 0],
rotation: 0, rotation: 0,
text: 'hi', text: ' ',
style: defaultStyle, style: defaultStyle,
} }
create(props: Partial<TextShape>): TextShape {
const shape = { ...this.defaultProps, ...props }
const bounds = this.getBounds(shape)
shape.point = Vec.sub(shape.point, [bounds.width / 2, bounds.height / 2])
return shape
}
shouldRender(prev: TextShape, next: TextShape): boolean { shouldRender(prev: TextShape, next: TextShape): boolean {
return ( return (
next.text !== prev.text || next.style.scale !== prev.style.scale || next.style !== prev.style next.text !== prev.text || next.style.scale !== prev.style.scale || next.style !== prev.style
@ -149,7 +156,7 @@ export class Text extends TLDrawShapeUtil<TextShape> {
} }
const fontSize = getFontSize(shape.style.size) * (shape.style.scale || 1) const fontSize = getFontSize(shape.style.size) * (shape.style.scale || 1)
const lineHeight = fontSize * 1.4 const lineHeight = fontSize * 1.3
if (!isEditing) { if (!isEditing) {
return ( return (
@ -168,7 +175,7 @@ export class Text extends TLDrawShapeUtil<TextShape> {
key={i} key={i}
x={4} x={4}
y={4 + fontSize / 2 + i * lineHeight} y={4 + fontSize / 2 + i * lineHeight}
fontFamily="Verveine Regular" fontFamily="Caveat Brush"
fontStyle="normal" fontStyle="normal"
fontWeight="500" fontWeight="500"
fontSize={fontSize} fontSize={fontSize}
@ -237,15 +244,16 @@ export class Text extends TLDrawShapeUtil<TextShape> {
getBounds(shape: TextShape): TLBounds { getBounds(shape: TextShape): TLBounds {
const bounds = Utils.getFromCache(this.boundsCache, shape, () => { const bounds = Utils.getFromCache(this.boundsCache, shape, () => {
if (!mdiv) { if (!melm) {
console.log('no melm!')
// We're in SSR // We're in SSR
return { minX: 0, minY: 0, maxX: 0, maxY: 0, width: 0, height: 0 } return { minX: 0, minY: 0, maxX: 0, maxY: 0, width: 0, height: 0 }
} }
mdiv.innerHTML = `${shape.text}&zwj;` melm.innerHTML = `${shape.text}&zwj;`
mdiv.style.font = getFontStyle(shape.style) melm.style.font = getFontStyle(shape.style)
const [width, height] = [mdiv.offsetWidth, mdiv.offsetHeight] const [width, height] = [melm.offsetWidth, melm.offsetHeight]
return { return {
minX: 0, minX: 0,

View file

@ -16,7 +16,7 @@ describe('Text session', () => {
}) })
) )
.select('text1') .select('text1')
.startTextSession() .startTextSession('text1')
.updateTextSession('Hello world') .updateTextSession('Hello world')
.completeSession() .completeSession()
.undo() .undo()
@ -35,7 +35,7 @@ describe('Text session', () => {
}) })
) )
.select('text1') .select('text1')
.startTextSession() .startTextSession('text1')
.updateTextSession('Hello world') .updateTextSession('Hello world')
.cancelSession() .cancelSession()

View file

@ -85,6 +85,51 @@ export class TextSession implements Session {
const shape = TLDR.getShape<TextShape>(data, initialShape.id, pageId) const shape = TLDR.getShape<TextShape>(data, initialShape.id, pageId)
// TODO: Delete text shape if its content is empty
// TODO: ...and prevent `isCreating` from selecting the deleted shape
// if (initialShape.text.trim() === '' && shape.text.trim() === '') {
// // delete shape
// console.log('deleting shape')
// return {
// id: 'text',
// before: {
// document: {
// pages: {
// [pageId]: {
// shapes: {
// [initialShape.id]: undefined,
// },
// },
// },
// pageState: {
// [pageId]: {
// editingId: undefined,
// selectedIds: [initialShape.id],
// },
// },
// },
// },
// after: {
// document: {
// pages: {
// [pageId]: {
// shapes: {
// [initialShape.id]: undefined,
// },
// },
// },
// pageState: {
// [pageId]: {
// editingId: undefined,
// selectedIds: [],
// },
// },
// },
// },
// }
// }
if (shape.text === initialShape.text) return undefined if (shape.text === initialShape.text) return undefined
return { return {
@ -121,6 +166,7 @@ export class TextSession implements Session {
pageState: { pageState: {
[pageId]: { [pageId]: {
editingId: undefined, editingId: undefined,
selectedIds: [initialShape.id],
}, },
}, },
}, },

View file

@ -91,15 +91,48 @@ const initialData: Data = {
export class TLDrawState extends StateManager<Data> { export class TLDrawState extends StateManager<Data> {
_onChange?: (tlstate: TLDrawState, data: Data, reason: string) => void _onChange?: (tlstate: TLDrawState, data: Data, reason: string) => void
constructor(id = Utils.uniqueId()) {
super(initialData, id, 1)
}
selectHistory: SelectHistory = { selectHistory: SelectHistory = {
stack: [[]], stack: [[]],
pointer: 0, pointer: 0,
} }
clipboard?: TLDrawShape[]
session?: Session
pointedId?: string
pointedHandle?: string
pointedBoundsHandle?: TLBoundsCorner | TLBoundsEdge | 'rotate'
isCreating = false
constructor(id = Utils.uniqueId()) {
super(initialData, id, 1)
this.session = undefined
this.pointedId = undefined
this.patchState({
appState: {
status: {
current: TLDrawStatus.Idle,
previous: TLDrawStatus.Idle,
},
},
document: {
pageStates: {
[this.currentPageId]: {
bindingId: undefined,
editingId: undefined,
hoveredId: undefined,
pointedId: undefined,
},
},
},
})
}
/* -------------------- Internal -------------------- */ /* -------------------- Internal -------------------- */
protected onStateWillChange = (_state: Data, id: string): void => { protected onStateWillChange = (_state: Data, id: string): void => {
@ -232,18 +265,6 @@ export class TLDrawState extends StateManager<Data> {
}) })
} }
clipboard?: TLDrawShape[]
session?: Session
pointedId?: string
pointedHandle?: string
pointedBoundsHandle?: TLBoundsCorner | TLBoundsEdge | 'rotate'
isCreating = false
/* -------------------- Settings -------------------- */ /* -------------------- Settings -------------------- */
togglePenMode = (): this => { togglePenMode = (): this => {
@ -787,6 +808,7 @@ export class TLDrawState extends StateManager<Data> {
completeSession<T extends Session>(...args: ParametersExceptFirst<T['complete']>) { completeSession<T extends Session>(...args: ParametersExceptFirst<T['complete']>) {
const { session } = this const { session } = this
if (!session) return this if (!session) return this
const result = session.complete(this.state, ...args) const result = session.complete(this.state, ...args)
@ -1531,6 +1553,11 @@ export class TLDrawState extends StateManager<Data> {
break break
} }
} }
break
}
case TLDrawStatus.EditingText: {
this.completeSession()
break
} }
} }
} }
@ -1624,10 +1651,6 @@ export class TLDrawState extends StateManager<Data> {
} }
break break
} }
case TLDrawStatus.EditingText: {
this.completeSession()
break
}
} }
} }
@ -1698,8 +1721,18 @@ export class TLDrawState extends StateManager<Data> {
// Unused // Unused
} }
onDoubleClickShape: TLPointerEventHandler = () => { onDoubleClickShape: TLPointerEventHandler = (info) => {
// TODO (drill into group) switch (this.appState.status.current) {
case TLDrawStatus.PointingBounds: {
switch (this.appState.activeTool) {
case 'select': {
this.startTextSession(info.target)
break
}
}
break
}
}
} }
onRightPointShape: TLPointerEventHandler = (info) => { onRightPointShape: TLPointerEventHandler = (info) => {
@ -1753,11 +1786,11 @@ export class TLDrawState extends StateManager<Data> {
} }
onDoubleClickBounds: TLBoundsEventHandler = () => { onDoubleClickBounds: TLBoundsEventHandler = () => {
// TODO // Unused
} }
onRightPointBounds: TLBoundsEventHandler = () => { onRightPointBounds: TLBoundsEventHandler = () => {
// TODO // Unused
} }
onDragBounds: TLBoundsEventHandler = () => { onDragBounds: TLBoundsEventHandler = () => {
@ -1765,11 +1798,11 @@ export class TLDrawState extends StateManager<Data> {
} }
onHoverBounds: TLBoundsEventHandler = () => { onHoverBounds: TLBoundsEventHandler = () => {
// TODO // Unused
} }
onUnhoverBounds: TLBoundsEventHandler = () => { onUnhoverBounds: TLBoundsEventHandler = () => {
// TODO // Unused
} }
onReleaseBounds: TLBoundsEventHandler = (info) => { onReleaseBounds: TLBoundsEventHandler = (info) => {

View file

@ -99,8 +99,6 @@ const { styled, css, theme, getCssString } = createCss({
}, },
}) })
const light = theme({})
const dark = theme({ const dark = theme({
colors: { colors: {
brushFill: 'rgba(180, 180, 180, .05)', brushFill: 'rgba(180, 180, 180, .05)',
@ -138,4 +136,4 @@ const dark = theme({
export default styled export default styled
export { css, getCssString, light, dark } export { css, getCssString, dark }