mirror of
https://github.com/logseq/logseq.git
synced 2026-05-28 06:34:34 +00:00
dev: tldraw preview button in demo
This commit is contained in:
@@ -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>
|
||||
)
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user