Browse Source

dev: tldraw preview button in demo

Peng Xiao 3 years ago
parent
commit
cd0be06d47

+ 1 - 1
tldraw/apps/tldraw-logseq/src/components/StatusBar/StatusBar.tsx

@@ -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>
   )
 })

+ 8 - 8
tldraw/apps/tldraw-logseq/src/lib/preview-manager.tsx

@@ -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)
 }

+ 68 - 15
tldraw/demo/src/App.jsx

@@ -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>
   )