diff --git a/packages/dev/esbuild.config.mjs b/packages/dev/esbuild.config.mjs index bc5dce38a..83116f0f4 100644 --- a/packages/dev/esbuild.config.mjs +++ b/packages/dev/esbuild.config.mjs @@ -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"', diff --git a/packages/dev/package.json b/packages/dev/package.json index 6e7e65326..897248336 100644 --- a/packages/dev/package.json +++ b/packages/dev/package.json @@ -34,4 +34,4 @@ "typescript": "4.2.3" }, "gitHead": "a7dac0f83ad998e205c2aab58182cb4ba4e099a6" -} +} \ No newline at end of file diff --git a/packages/dev/src/assets/VerveineRegular.woff b/packages/dev/src/assets/VerveineRegular.woff new file mode 100644 index 000000000..098d9fb17 Binary files /dev/null and b/packages/dev/src/assets/VerveineRegular.woff differ diff --git a/packages/dev/src/components/editor.tsx b/packages/dev/src/components/editor.tsx index c699c8e7a..c242be516 100644 --- a/packages/dev/src/components/editor.tsx +++ b/packages/dev/src/components/editor.tsx @@ -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
- // } - - // return - - // Will automatically persist data under the provided id, too return } diff --git a/packages/tldraw/scripts/build.js b/packages/tldraw/scripts/build.js index 9bfb93f40..a1efb7642 100644 --- a/packages/tldraw/scripts/build.js +++ b/packages/tldraw/scripts/build.js @@ -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.`) diff --git a/packages/tldraw/scripts/dev.js b/packages/tldraw/scripts/dev.js index 2e788bcbf..d300f6f57 100644 --- a/packages/tldraw/scripts/dev.js +++ b/packages/tldraw/scripts/dev.js @@ -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', diff --git a/packages/tldraw/scripts/pre-dev.js b/packages/tldraw/scripts/pre-dev.js index a8c513301..88a2c39f7 100644 --- a/packages/tldraw/scripts/pre-dev.js +++ b/packages/tldraw/scripts/pre-dev.js @@ -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() diff --git a/packages/tldraw/src/assets/VerveineRegular.woff b/packages/tldraw/src/assets/VerveineRegular.woff new file mode 100644 index 000000000..098d9fb17 Binary files /dev/null and b/packages/tldraw/src/assets/VerveineRegular.woff differ diff --git a/packages/tldraw/src/components/tldraw/tldraw.tsx b/packages/tldraw/src/components/tldraw/tldraw.tsx index f52d3cb74..8a62aab3e 100644 --- a/packages/tldraw/src/components/tldraw/tldraw.tsx +++ b/packages/tldraw/src/components/tldraw/tldraw.tsx @@ -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) diff --git a/packages/tldraw/src/hooks/index.ts b/packages/tldraw/src/hooks/index.ts index 37f885098..bf38556de 100644 --- a/packages/tldraw/src/hooks/index.ts +++ b/packages/tldraw/src/hooks/index.ts @@ -1,3 +1,4 @@ export * from './useKeyboardShortcuts' export * from './useTLDrawContext' export * from './useTheme' +export * from './useCustomFonts' diff --git a/packages/tldraw/src/hooks/useCustomFonts.ts b/packages/tldraw/src/hooks/useCustomFonts.ts new file mode 100644 index 000000000..99944f5b5 --- /dev/null +++ b/packages/tldraw/src/hooks/useCustomFonts.ts @@ -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) +} diff --git a/packages/tldraw/src/hooks/useStyle.ts b/packages/tldraw/src/hooks/useStyle.ts new file mode 100644 index 000000000..7aa82eb24 --- /dev/null +++ b/packages/tldraw/src/hooks/useStyle.ts @@ -0,0 +1,30 @@ +import * as React from 'react' + +const styles = new Map() + +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]) +} diff --git a/packages/tldraw/src/shape/shape-styles.ts b/packages/tldraw/src/shape/shape-styles.ts index d1d3c3800..5751e9d6f 100644 --- a/packages/tldraw/src/shape/shape-styles.ts +++ b/packages/tldraw/src/shape/shape-styles.ts @@ -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( diff --git a/packages/tldraw/src/shape/shapes/text/text.tsx b/packages/tldraw/src/shape/shapes/text/text.tsx index 2a5fe49d4..352cccb0c 100644 --- a/packages/tldraw/src/shape/shapes/text/text.tsx +++ b/packages/tldraw/src/shape/shapes/text/text.tsx @@ -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 { @@ -69,10 +69,17 @@ export class Text extends TLDrawShapeUtil { childIndex: 1, point: [0, 0], rotation: 0, - text: 'hi', + text: ' ', style: defaultStyle, } + create(props: Partial): 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 { } 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 { 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 { 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, diff --git a/packages/tldraw/src/state/session/sessions/text/text.session.spec.ts b/packages/tldraw/src/state/session/sessions/text/text.session.spec.ts index 86a3e85ae..70d0f7e79 100644 --- a/packages/tldraw/src/state/session/sessions/text/text.session.spec.ts +++ b/packages/tldraw/src/state/session/sessions/text/text.session.spec.ts @@ -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() diff --git a/packages/tldraw/src/state/session/sessions/text/text.session.ts b/packages/tldraw/src/state/session/sessions/text/text.session.ts index 13cd36f6b..9f2487a49 100644 --- a/packages/tldraw/src/state/session/sessions/text/text.session.ts +++ b/packages/tldraw/src/state/session/sessions/text/text.session.ts @@ -85,6 +85,51 @@ export class TextSession implements Session { const shape = TLDR.getShape(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], }, }, }, diff --git a/packages/tldraw/src/state/tlstate.ts b/packages/tldraw/src/state/tlstate.ts index 6f46baa3f..d76427a6c 100644 --- a/packages/tldraw/src/state/tlstate.ts +++ b/packages/tldraw/src/state/tlstate.ts @@ -91,15 +91,48 @@ const initialData: Data = { export class TLDrawState extends StateManager { _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 { }) } - 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 { completeSession(...args: ParametersExceptFirst) { const { session } = this + if (!session) return this const result = session.complete(this.state, ...args) @@ -1531,6 +1553,11 @@ export class TLDrawState extends StateManager { break } } + break + } + case TLDrawStatus.EditingText: { + this.completeSession() + break } } } @@ -1624,10 +1651,6 @@ export class TLDrawState extends StateManager { } break } - case TLDrawStatus.EditingText: { - this.completeSession() - break - } } } @@ -1698,8 +1721,18 @@ export class TLDrawState extends StateManager { // 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 { } onDoubleClickBounds: TLBoundsEventHandler = () => { - // TODO + // Unused } onRightPointBounds: TLBoundsEventHandler = () => { - // TODO + // Unused } onDragBounds: TLBoundsEventHandler = () => { @@ -1765,11 +1798,11 @@ export class TLDrawState extends StateManager { } onHoverBounds: TLBoundsEventHandler = () => { - // TODO + // Unused } onUnhoverBounds: TLBoundsEventHandler = () => { - // TODO + // Unused } onReleaseBounds: TLBoundsEventHandler = (info) => { diff --git a/packages/tldraw/src/styles/stitches.config.ts b/packages/tldraw/src/styles/stitches.config.ts index 9534c65d9..d48dfb840 100644 --- a/packages/tldraw/src/styles/stitches.config.ts +++ b/packages/tldraw/src/styles/stitches.config.ts @@ -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 }