Adds text again
This commit is contained in:
parent
c28440b878
commit
8fe83ef6c3
18 changed files with 207 additions and 163 deletions
|
@ -21,6 +21,10 @@ esbuild
|
|||
minify: false,
|
||||
sourcemap: true,
|
||||
incremental: isDevServer,
|
||||
assetNames: 'assets/[name]-[hash]',
|
||||
loader: {
|
||||
'.woff': 'file',
|
||||
},
|
||||
target: ['chrome58', 'firefox57', 'safari11', 'edge18'],
|
||||
define: {
|
||||
'process.env.NODE_ENV': isDevServer ? '"development"' : '"production"',
|
||||
|
|
BIN
packages/dev/src/assets/VerveineRegular.woff
Normal file
BIN
packages/dev/src/assets/VerveineRegular.woff
Normal file
Binary file not shown.
|
@ -1,110 +1,6 @@
|
|||
import * as React from 'react'
|
||||
import {
|
||||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
import { TLDraw } from '@tldraw/tldraw'
|
||||
|
||||
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" />
|
||||
}
|
||||
|
|
|
@ -9,6 +9,14 @@ async function main() {
|
|||
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 {
|
||||
esbuild.buildSync({
|
||||
entryPoints: ['./src/index.ts'],
|
||||
|
@ -36,13 +44,6 @@ async function main() {
|
|||
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.`)
|
||||
} catch (e) {
|
||||
console.log(`× ${name}: Build failed due to an error.`)
|
||||
|
|
|
@ -9,8 +9,8 @@ async function main() {
|
|||
outdir: 'dist/cjs',
|
||||
minify: false,
|
||||
bundle: true,
|
||||
format: 'esm',
|
||||
target: 'cjs',
|
||||
format: 'cjs',
|
||||
target: 'es6',
|
||||
jsxFactory: 'React.createElement',
|
||||
jsxFragment: 'React.Fragment',
|
||||
tsconfig: './tsconfig.build.json',
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/* eslint-disable */
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const esbuild = require('esbuild')
|
||||
|
||||
async function main() {
|
||||
|
@ -21,6 +22,12 @@ async function main() {
|
|||
tsconfig: './tsconfig.build.json',
|
||||
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()
|
||||
|
|
BIN
packages/tldraw/src/assets/VerveineRegular.woff
Normal file
BIN
packages/tldraw/src/assets/VerveineRegular.woff
Normal file
Binary file not shown.
|
@ -4,7 +4,7 @@ import { Renderer } from '@tldraw/core'
|
|||
import styled from '~styles'
|
||||
import type { Data, TLDrawDocument } from '~types'
|
||||
import { TLDrawState } from '~state'
|
||||
import { useKeyboardShortcuts, TLDrawContext } from '~hooks'
|
||||
import { useKeyboardShortcuts, TLDrawContext, useCustomFonts } from '~hooks'
|
||||
import { tldrawShapeUtils } from '~shape'
|
||||
import { ContextMenu } from '~components/context-menu'
|
||||
import { StylePanel } from '~components/style-panel'
|
||||
|
@ -63,6 +63,8 @@ export function TLDraw({ id, document, currentPageId, onMount, onChange: _onChan
|
|||
|
||||
useKeyboardShortcuts(tlstate)
|
||||
|
||||
useCustomFonts()
|
||||
|
||||
const page = context.useSelector(pageSelector)
|
||||
|
||||
const pageState = context.useSelector(pageStateSelector)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export * from './useKeyboardShortcuts'
|
||||
export * from './useTLDrawContext'
|
||||
export * from './useTheme'
|
||||
export * from './useCustomFonts'
|
||||
|
|
18
packages/tldraw/src/hooks/useCustomFonts.ts
Normal file
18
packages/tldraw/src/hooks/useCustomFonts.ts
Normal 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)
|
||||
}
|
30
packages/tldraw/src/hooks/useStyle.ts
Normal file
30
packages/tldraw/src/hooks/useStyle.ts
Normal 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])
|
||||
}
|
|
@ -69,7 +69,7 @@ export function getFontStyle(style: ShapeStyles): string {
|
|||
const fontSize = getFontSize(style.size)
|
||||
const { scale = 1 } = style
|
||||
|
||||
return `${fontSize * scale}px/1.4 Verveine Regular`
|
||||
return `${fontSize * scale}px/1.3 "Caveat Brush"`
|
||||
}
|
||||
|
||||
export function getShapeStyle(
|
||||
|
|
|
@ -16,16 +16,16 @@ function normalizeText(text: string) {
|
|||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let mdiv: any
|
||||
let melm: any
|
||||
|
||||
function getMeasurementDiv() {
|
||||
// A div used for measurement
|
||||
document.getElementById('__textMeasure')?.remove()
|
||||
|
||||
const mdiv = document.createElement('pre')
|
||||
mdiv.id = '__textMeasure'
|
||||
const pre = document.createElement('pre')
|
||||
pre.id = '__textMeasure'
|
||||
|
||||
Object.assign(mdiv.style, {
|
||||
Object.assign(pre.style, {
|
||||
whiteSpace: 'pre',
|
||||
width: 'auto',
|
||||
border: '1px solid red',
|
||||
|
@ -42,14 +42,14 @@ function getMeasurementDiv() {
|
|||
dominantBaseline: 'mathematical',
|
||||
})
|
||||
|
||||
mdiv.tabIndex = -1
|
||||
pre.tabIndex = -1
|
||||
|
||||
document.body.appendChild(mdiv)
|
||||
return mdiv
|
||||
document.body.appendChild(pre)
|
||||
return pre
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
getMeasurementDiv()
|
||||
melm = getMeasurementDiv()
|
||||
}
|
||||
|
||||
export class Text extends TLDrawShapeUtil<TextShape> {
|
||||
|
@ -69,10 +69,17 @@ export class Text extends TLDrawShapeUtil<TextShape> {
|
|||
childIndex: 1,
|
||||
point: [0, 0],
|
||||
rotation: 0,
|
||||
text: 'hi',
|
||||
text: ' ',
|
||||
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 {
|
||||
return (
|
||||
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 lineHeight = fontSize * 1.4
|
||||
const lineHeight = fontSize * 1.3
|
||||
|
||||
if (!isEditing) {
|
||||
return (
|
||||
|
@ -168,7 +175,7 @@ export class Text extends TLDrawShapeUtil<TextShape> {
|
|||
key={i}
|
||||
x={4}
|
||||
y={4 + fontSize / 2 + i * lineHeight}
|
||||
fontFamily="Verveine Regular"
|
||||
fontFamily="Caveat Brush"
|
||||
fontStyle="normal"
|
||||
fontWeight="500"
|
||||
fontSize={fontSize}
|
||||
|
@ -237,15 +244,16 @@ export class Text extends TLDrawShapeUtil<TextShape> {
|
|||
|
||||
getBounds(shape: TextShape): TLBounds {
|
||||
const bounds = Utils.getFromCache(this.boundsCache, shape, () => {
|
||||
if (!mdiv) {
|
||||
if (!melm) {
|
||||
console.log('no melm!')
|
||||
// We're in SSR
|
||||
return { minX: 0, minY: 0, maxX: 0, maxY: 0, width: 0, height: 0 }
|
||||
}
|
||||
|
||||
mdiv.innerHTML = `${shape.text}‍`
|
||||
mdiv.style.font = getFontStyle(shape.style)
|
||||
melm.innerHTML = `${shape.text}‍`
|
||||
melm.style.font = getFontStyle(shape.style)
|
||||
|
||||
const [width, height] = [mdiv.offsetWidth, mdiv.offsetHeight]
|
||||
const [width, height] = [melm.offsetWidth, melm.offsetHeight]
|
||||
|
||||
return {
|
||||
minX: 0,
|
||||
|
|
|
@ -16,7 +16,7 @@ describe('Text session', () => {
|
|||
})
|
||||
)
|
||||
.select('text1')
|
||||
.startTextSession()
|
||||
.startTextSession('text1')
|
||||
.updateTextSession('Hello world')
|
||||
.completeSession()
|
||||
.undo()
|
||||
|
@ -35,7 +35,7 @@ describe('Text session', () => {
|
|||
})
|
||||
)
|
||||
.select('text1')
|
||||
.startTextSession()
|
||||
.startTextSession('text1')
|
||||
.updateTextSession('Hello world')
|
||||
.cancelSession()
|
||||
|
||||
|
|
|
@ -85,6 +85,51 @@ export class TextSession implements Session {
|
|||
|
||||
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
|
||||
|
||||
return {
|
||||
|
@ -121,6 +166,7 @@ export class TextSession implements Session {
|
|||
pageState: {
|
||||
[pageId]: {
|
||||
editingId: undefined,
|
||||
selectedIds: [initialShape.id],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -91,15 +91,48 @@ const initialData: Data = {
|
|||
export class TLDrawState extends StateManager<Data> {
|
||||
_onChange?: (tlstate: TLDrawState, data: Data, reason: string) => void
|
||||
|
||||
constructor(id = Utils.uniqueId()) {
|
||||
super(initialData, id, 1)
|
||||
}
|
||||
|
||||
selectHistory: SelectHistory = {
|
||||
stack: [[]],
|
||||
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 -------------------- */
|
||||
|
||||
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 -------------------- */
|
||||
|
||||
togglePenMode = (): this => {
|
||||
|
@ -787,6 +808,7 @@ export class TLDrawState extends StateManager<Data> {
|
|||
|
||||
completeSession<T extends Session>(...args: ParametersExceptFirst<T['complete']>) {
|
||||
const { session } = this
|
||||
|
||||
if (!session) return this
|
||||
|
||||
const result = session.complete(this.state, ...args)
|
||||
|
@ -1531,6 +1553,11 @@ export class TLDrawState extends StateManager<Data> {
|
|||
break
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
case TLDrawStatus.EditingText: {
|
||||
this.completeSession()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1624,10 +1651,6 @@ export class TLDrawState extends StateManager<Data> {
|
|||
}
|
||||
break
|
||||
}
|
||||
case TLDrawStatus.EditingText: {
|
||||
this.completeSession()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1698,8 +1721,18 @@ export class TLDrawState extends StateManager<Data> {
|
|||
// Unused
|
||||
}
|
||||
|
||||
onDoubleClickShape: TLPointerEventHandler = () => {
|
||||
// TODO (drill into group)
|
||||
onDoubleClickShape: TLPointerEventHandler = (info) => {
|
||||
switch (this.appState.status.current) {
|
||||
case TLDrawStatus.PointingBounds: {
|
||||
switch (this.appState.activeTool) {
|
||||
case 'select': {
|
||||
this.startTextSession(info.target)
|
||||
break
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onRightPointShape: TLPointerEventHandler = (info) => {
|
||||
|
@ -1753,11 +1786,11 @@ export class TLDrawState extends StateManager<Data> {
|
|||
}
|
||||
|
||||
onDoubleClickBounds: TLBoundsEventHandler = () => {
|
||||
// TODO
|
||||
// Unused
|
||||
}
|
||||
|
||||
onRightPointBounds: TLBoundsEventHandler = () => {
|
||||
// TODO
|
||||
// Unused
|
||||
}
|
||||
|
||||
onDragBounds: TLBoundsEventHandler = () => {
|
||||
|
@ -1765,11 +1798,11 @@ export class TLDrawState extends StateManager<Data> {
|
|||
}
|
||||
|
||||
onHoverBounds: TLBoundsEventHandler = () => {
|
||||
// TODO
|
||||
// Unused
|
||||
}
|
||||
|
||||
onUnhoverBounds: TLBoundsEventHandler = () => {
|
||||
// TODO
|
||||
// Unused
|
||||
}
|
||||
|
||||
onReleaseBounds: TLBoundsEventHandler = (info) => {
|
||||
|
|
|
@ -99,8 +99,6 @@ const { styled, css, theme, getCssString } = createCss({
|
|||
},
|
||||
})
|
||||
|
||||
const light = theme({})
|
||||
|
||||
const dark = theme({
|
||||
colors: {
|
||||
brushFill: 'rgba(180, 180, 180, .05)',
|
||||
|
@ -138,4 +136,4 @@ const dark = theme({
|
|||
|
||||
export default styled
|
||||
|
||||
export { css, getCssString, light, dark }
|
||||
export { css, getCssString, dark }
|
||||
|
|
Loading…
Reference in a new issue