dev: tldraw preview button in demo

This commit is contained in:
Peng Xiao
2022-11-16 15:53:29 +08:00
parent 065e2e2ca8
commit cd0be06d47
3 changed files with 77 additions and 24 deletions

View File

@@ -32,7 +32,7 @@ export const StatusBar = observer(function StatusBar() {
<div className="tl-statusbar">
{app.selectedTool.id} | {app.selectedTool.currentState.id}
<div style={{ flex: 1 }} />
<div id="tl-statusbar-anchor" style={{ display: 'flex' }} />
<div id="tl-statusbar-anchor" className='flex gap-1' />
</div>
)
})

View File

@@ -33,7 +33,7 @@ export class PreviewManager {
})
}
generatePreviewJsx(viewport?: TLViewport) {
generatePreviewJsx(viewport?: TLViewport, ratio?: number) {
const allBounds = [...(this.shapes ?? []).map(s => s.getRotatedBounds())]
const vBounds = viewport?.currentView
if (vBounds) {
@@ -47,7 +47,7 @@ export class PreviewManager {
commonBounds = BoundsUtils.expandBounds(commonBounds, SVG_EXPORT_PADDING)
// make sure commonBounds is of ratio 4/3 (should we have another ratio setting?)
commonBounds = viewport ? BoundsUtils.ensureRatio(commonBounds, 4 / 3) : commonBounds
commonBounds = ratio ? BoundsUtils.ensureRatio(commonBounds, ratio) : commonBounds
const translatePoint = (p: [number, number]): [string, string] => {
return [(p[0] - commonBounds.minX).toFixed(2), (p[1] - commonBounds.minY).toFixed(2)]
@@ -123,8 +123,8 @@ export class PreviewManager {
return svgElement
}
exportAsSVG() {
const svgElement = this.generatePreviewJsx()
exportAsSVG(ratio: number) {
const svgElement = this.generatePreviewJsx(undefined, ratio)
return svgElement ? ReactDOMServer.renderToString(svgElement) : ''
}
}
@@ -134,12 +134,12 @@ export class PreviewManager {
*
* @param serializedApp
*/
export function generateSVGFromApp(serializedApp: TLDocumentModel<Shape>) {
export function generateSVGFromApp(serializedApp: TLDocumentModel<Shape>, ratio = 4 / 3) {
const preview = new PreviewManager(serializedApp)
return preview.exportAsSVG()
return preview.exportAsSVG(ratio)
}
export function generateJSXFromApp(serializedApp: TLDocumentModel<Shape>) {
export function generateJSXFromApp(serializedApp: TLDocumentModel<Shape>, ratio = 4 / 3) {
const preview = new PreviewManager(serializedApp)
return preview.generatePreviewJsx()
return preview.generatePreviewJsx(undefined, ratio)
}

View File

@@ -1,7 +1,7 @@
import { uniqueId, fileToBase64 } from '@tldraw/core'
import React from 'react'
import ReactDOM from 'react-dom'
import { App as TldrawApp } from '@tldraw/logseq'
import { App as TldrawApp, generateJSXFromApp } from '@tldraw/logseq'
const storingKey = 'playground.index'
@@ -83,16 +83,17 @@ const PageNameLink = props => {
)
}
const ThemeSwitcher = ({ theme, setTheme }) => {
const StatusBarSwitcher = ({ label, onClick }) => {
const [anchor, setAnchor] = React.useState(null)
React.useEffect(() => {
if (anchor) {
return
}
let el = document.querySelector('#theme-switcher')
const id = 'status-bar-switcher-' + uniqueId()
let el = document.getElementById(id)
if (!el) {
el = document.createElement('div')
el.id = 'theme-switcher'
el.id = id
let timer = setInterval(() => {
const statusBarAnchor = document.querySelector('#tl-statusbar-anchor')
if (statusBarAnchor) {
@@ -104,26 +105,76 @@ const ThemeSwitcher = ({ theme, setTheme }) => {
}
})
React.useEffect(() => {
document.documentElement.setAttribute('data-theme', theme)
}, [theme])
if (!anchor) {
return null
}
return ReactDOM.createPortal(
<button
className="flex items-center justify-center mx-2 bg-grey"
className="flex items-center justify-center bg-grey border px-1"
style={{ fontSize: '1em' }}
onClick={() => setTheme(t => (t === 'dark' ? 'light' : 'dark'))}
onClick={onClick}
>
{theme} theme
{label}
</button>,
anchor
)
}
const ThemeSwitcher = () => {
const [theme, setTheme] = React.useState('light')
React.useEffect(() => {
document.documentElement.setAttribute('data-theme', theme)
}, [theme])
return (
<StatusBarSwitcher
label={theme + ' theme'}
onClick={() => {
setTheme(t => (t === 'dark' ? 'light' : 'dark'))
}}
/>
)
}
const PreviewButton = ({ model }) => {
const [show, setShow] = React.useState(false)
const [[w, h], setSize] = React.useState([window.innerWidth, window.innerHeight])
React.useEffect(() => {
const onResize = () => {
setSize([window.innerWidth, window.innerHeight])
}
window.addEventListener('resize', onResize)
return () => window.removeEventListener('resize', onResize)
}, [])
const preview = React.useMemo(() => {
return generateJSXFromApp(model, w / h)
}, [model, w, h])
return (
<>
{show ? (
<div
className="fixed inset-0 flex items-center justify-center pointer-events-none h-screen w-screen"
style={{ zIndex: '10000' }}
>
<div className="w-1/2 h-1/2 border bg-white">{preview}</div>
</div>
) : null}
<StatusBarSwitcher
label="Preview"
onClick={() => {
setShow(s => !s)
}}
/>
</>
)
}
const searchHandler = q => {
return Promise.resolve({
pages: ['foo', 'bar', 'asdf'].filter(p => p.includes(q)),
@@ -136,8 +187,6 @@ const searchHandler = q => {
}
export default function App() {
const [theme, setTheme] = React.useState('light')
const [model, setModel] = React.useState(documentModel)
// Mimic external reload event
@@ -153,7 +202,8 @@ export default function App() {
return (
<div className={`h-screen w-screen`}>
<ThemeSwitcher theme={theme} setTheme={setTheme} />
<ThemeSwitcher />
<PreviewButton model={model} />
<TldrawApp
renderers={{
Page,
@@ -170,7 +220,10 @@ export default function App() {
makeAssetUrl: a => a,
}}
model={model}
onPersist={onPersist}
onPersist={app => {
onPersist(app)
setModel(app.serialized)
}}
/>
</div>
)