restore root
This commit is contained in:
parent
005427a3f5
commit
8237d08c7f
210
src/app/page.tsx
210
src/app/page.tsx
|
|
@ -1,33 +1,185 @@
|
|||
/* eslint-disable @next/next/no-img-element */
|
||||
'use client'
|
||||
|
||||
import { LiveImageShape, LiveImageShapeUtil } from '@/components/LiveImageShapeUtil'
|
||||
import { LiveImageTool,MakeLiveButton } from '@/components/LiveImageTool'
|
||||
import { LockupLink } from '@/components/LockupLink'
|
||||
import { LiveImageProvider } from '@/hooks/useLiveImage'
|
||||
import * as fal from '@fal-ai/serverless-client'
|
||||
import {
|
||||
AssetRecordType,
|
||||
DefaultSizeStyle,
|
||||
Editor,
|
||||
TLUiOverrides,
|
||||
Tldraw,
|
||||
toolbarItem,
|
||||
track,
|
||||
useEditor,
|
||||
} from '@tldraw/tldraw'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
|
||||
fal.config({
|
||||
requestMiddleware: fal.withProxy({
|
||||
targetUrl: '/api/fal/proxy',
|
||||
}),
|
||||
})
|
||||
|
||||
const overrides: TLUiOverrides = {
|
||||
tools(editor, tools) {
|
||||
tools.liveImage = {
|
||||
id: 'live-image',
|
||||
icon: 'tool-frame',
|
||||
label: 'Frame',
|
||||
kbd: 'f',
|
||||
readonlyOk: false,
|
||||
onSelect: () => {
|
||||
editor.setCurrentTool('live-image')
|
||||
},
|
||||
}
|
||||
return tools
|
||||
},
|
||||
toolbar(_app, toolbar, { tools }) {
|
||||
const frameIndex = toolbar.findIndex((item) => item.id === 'frame')
|
||||
if (frameIndex !== -1) toolbar.splice(frameIndex, 1)
|
||||
const highlighterIndex = toolbar.findIndex((item) => item.id === 'highlight')
|
||||
if (highlighterIndex !== -1) {
|
||||
const highlighterItem = toolbar[highlighterIndex]
|
||||
toolbar.splice(highlighterIndex, 1)
|
||||
toolbar.splice(3, 0, highlighterItem)
|
||||
}
|
||||
toolbar.splice(2, 0, toolbarItem(tools.liveImage))
|
||||
return toolbar
|
||||
},
|
||||
}
|
||||
|
||||
const shapeUtils = [LiveImageShapeUtil]
|
||||
const tools = [LiveImageTool]
|
||||
|
||||
export default function Home() {
|
||||
const onEditorMount = (editor: Editor) => {
|
||||
// We need the editor to think that the live image shape is a frame
|
||||
// @ts-expect-error: patch
|
||||
editor.isShapeOfType = function (arg, type) {
|
||||
const shape = typeof arg === 'string' ? this.getShape(arg)! : arg
|
||||
if (shape.type === 'live-image' && type === 'frame') {
|
||||
return true
|
||||
}
|
||||
return shape.type === type
|
||||
}
|
||||
|
||||
// If there isn't a live image shape, create one
|
||||
if (!editor.getCurrentPageShapes().some((shape) => shape.type === 'live-image')) {
|
||||
editor.createShape<LiveImageShape>({
|
||||
type: 'live-image',
|
||||
x: 120,
|
||||
y: 180,
|
||||
props: {
|
||||
w: 512,
|
||||
h: 512,
|
||||
name: '',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
editor.setStyleForNextShapes(DefaultSizeStyle, 'xl', { ephemeral: true })
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="sorry">
|
||||
<div className="sorry__inner">
|
||||
<p className="sorry__lockup">
|
||||
<a href="https://twitter.com/tldraw" target="_blank" rel="nofollow noopener">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img className="tldraw" src="/Lockup-Black.svg" alt="by tldraw" />
|
||||
</a>
|
||||
</p>
|
||||
<h1>Sorry!</h1>
|
||||
<p>We shut this project down on January 14th, 2024.</p>
|
||||
<p>Here are some things you can do:</p>
|
||||
<ol>
|
||||
<li>
|
||||
<a href="https://twitter.com/tldraw/status/1727728068968460778">Learn more</a> about
|
||||
what this project was.
|
||||
</li>
|
||||
<li>
|
||||
Visit <a href="https://tldraw.com">tldraw.com</a> for a free multiplayer whiteboard.
|
||||
</li>
|
||||
<li>
|
||||
Let us know on the <a href="https://discord.gg/dJr8yJGkT5">tldraw Discord</a> if you really really liked this.
|
||||
</li>
|
||||
</ol>
|
||||
<p>Thank you!</p>
|
||||
<p>
|
||||
❤️ <a href="https://x.com/tldraw">tldraw</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<LiveImageProvider appId="110602490-lcm-sd15-i2i">
|
||||
<main className="tldraw-wrapper">
|
||||
<div className="tldraw-wrapper__inner">
|
||||
<Tldraw
|
||||
persistenceKey="tldraw-fal"
|
||||
onMount={onEditorMount}
|
||||
shapeUtils={shapeUtils}
|
||||
tools={tools}
|
||||
shareZone={<MakeLiveButton />}
|
||||
overrides={overrides}
|
||||
>
|
||||
<SneakySideEffects />
|
||||
<LockupLink />
|
||||
<LiveImageAssets />
|
||||
</Tldraw>
|
||||
</div>
|
||||
</main>
|
||||
</LiveImageProvider>
|
||||
)
|
||||
}
|
||||
|
||||
function SneakySideEffects() {
|
||||
const editor = useEditor()
|
||||
|
||||
useEffect(() => {
|
||||
editor.sideEffects.registerAfterChangeHandler('shape', () => {
|
||||
editor.emit('update-drawings' as any)
|
||||
})
|
||||
editor.sideEffects.registerAfterCreateHandler('shape', () => {
|
||||
editor.emit('update-drawings' as any)
|
||||
})
|
||||
editor.sideEffects.registerAfterDeleteHandler('shape', () => {
|
||||
editor.emit('update-drawings' as any)
|
||||
})
|
||||
}, [editor])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
const LiveImageAssets = track(function LiveImageAssets() {
|
||||
const editor = useEditor()
|
||||
|
||||
return (
|
||||
<Inject selector=".tl-overlays .tl-html-layer">
|
||||
{editor
|
||||
.getCurrentPageShapes()
|
||||
.filter((shape): shape is LiveImageShape => shape.type === 'live-image')
|
||||
.map((shape) => (
|
||||
<LiveImageAsset key={shape.id} shape={shape} />
|
||||
))}
|
||||
</Inject>
|
||||
)
|
||||
})
|
||||
|
||||
const LiveImageAsset = track(function LiveImageAsset({ shape }: { shape: LiveImageShape }) {
|
||||
const editor = useEditor()
|
||||
|
||||
if (!shape.props.overlayResult) return null
|
||||
|
||||
const transform = editor.getShapePageTransform(shape).toCssString()
|
||||
const assetId = AssetRecordType.createId(shape.id.split(':')[1])
|
||||
const asset = editor.getAsset(assetId)
|
||||
return (
|
||||
asset &&
|
||||
asset.props.src && (
|
||||
<img
|
||||
src={asset.props.src!}
|
||||
alt={shape.props.name}
|
||||
width={shape.props.w}
|
||||
height={shape.props.h}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: shape.props.w,
|
||||
height: shape.props.h,
|
||||
maxWidth: 'none',
|
||||
transform,
|
||||
transformOrigin: 'top left',
|
||||
opacity: shape.opacity,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
function Inject({ children, selector }: { children: React.ReactNode; selector: string }) {
|
||||
const [parent, setParent] = useState<Element | null>(null)
|
||||
const target = useMemo(() => parent?.querySelector(selector) ?? null, [parent, selector])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div ref={(el) => setParent(el?.parentElement ?? null)} style={{ display: 'none' }} />
|
||||
{target && createPortal(children, target)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,185 +0,0 @@
|
|||
/* eslint-disable @next/next/no-img-element */
|
||||
'use client'
|
||||
|
||||
import { LiveImageShape, LiveImageShapeUtil } from '@/components/LiveImageShapeUtil'
|
||||
import { LockupLink } from '@/components/LockupLink'
|
||||
import { LiveImageProvider } from '@/hooks/useLiveImage'
|
||||
import * as fal from '@fal-ai/serverless-client'
|
||||
import {
|
||||
AssetRecordType,
|
||||
DefaultSizeStyle,
|
||||
Editor,
|
||||
TLUiOverrides,
|
||||
Tldraw,
|
||||
toolbarItem,
|
||||
track,
|
||||
useEditor,
|
||||
} from '@tldraw/tldraw'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import { LiveImageTool, MakeLiveButton } from '../../components/LiveImageTool'
|
||||
|
||||
fal.config({
|
||||
requestMiddleware: fal.withProxy({
|
||||
targetUrl: '/api/fal/proxy',
|
||||
}),
|
||||
})
|
||||
|
||||
const overrides: TLUiOverrides = {
|
||||
tools(editor, tools) {
|
||||
tools.liveImage = {
|
||||
id: 'live-image',
|
||||
icon: 'tool-frame',
|
||||
label: 'Frame',
|
||||
kbd: 'f',
|
||||
readonlyOk: false,
|
||||
onSelect: () => {
|
||||
editor.setCurrentTool('live-image')
|
||||
},
|
||||
}
|
||||
return tools
|
||||
},
|
||||
toolbar(_app, toolbar, { tools }) {
|
||||
const frameIndex = toolbar.findIndex((item) => item.id === 'frame')
|
||||
if (frameIndex !== -1) toolbar.splice(frameIndex, 1)
|
||||
const highlighterIndex = toolbar.findIndex((item) => item.id === 'highlight')
|
||||
if (highlighterIndex !== -1) {
|
||||
const highlighterItem = toolbar[highlighterIndex]
|
||||
toolbar.splice(highlighterIndex, 1)
|
||||
toolbar.splice(3, 0, highlighterItem)
|
||||
}
|
||||
toolbar.splice(2, 0, toolbarItem(tools.liveImage))
|
||||
return toolbar
|
||||
},
|
||||
}
|
||||
|
||||
const shapeUtils = [LiveImageShapeUtil]
|
||||
const tools = [LiveImageTool]
|
||||
|
||||
export default function Home() {
|
||||
const onEditorMount = (editor: Editor) => {
|
||||
// We need the editor to think that the live image shape is a frame
|
||||
// @ts-expect-error: patch
|
||||
editor.isShapeOfType = function (arg, type) {
|
||||
const shape = typeof arg === 'string' ? this.getShape(arg)! : arg
|
||||
if (shape.type === 'live-image' && type === 'frame') {
|
||||
return true
|
||||
}
|
||||
return shape.type === type
|
||||
}
|
||||
|
||||
// If there isn't a live image shape, create one
|
||||
if (!editor.getCurrentPageShapes().some((shape) => shape.type === 'live-image')) {
|
||||
editor.createShape<LiveImageShape>({
|
||||
type: 'live-image',
|
||||
x: 120,
|
||||
y: 180,
|
||||
props: {
|
||||
w: 512,
|
||||
h: 512,
|
||||
name: '',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
editor.setStyleForNextShapes(DefaultSizeStyle, 'xl', { ephemeral: true })
|
||||
}
|
||||
|
||||
return (
|
||||
<LiveImageProvider appId="110602490-lcm-sd15-i2i">
|
||||
<main className="tldraw-wrapper">
|
||||
<div className="tldraw-wrapper__inner">
|
||||
<Tldraw
|
||||
persistenceKey="tldraw-fal"
|
||||
onMount={onEditorMount}
|
||||
shapeUtils={shapeUtils}
|
||||
tools={tools}
|
||||
shareZone={<MakeLiveButton />}
|
||||
overrides={overrides}
|
||||
>
|
||||
<SneakySideEffects />
|
||||
<LockupLink />
|
||||
<LiveImageAssets />
|
||||
</Tldraw>
|
||||
</div>
|
||||
</main>
|
||||
</LiveImageProvider>
|
||||
)
|
||||
}
|
||||
|
||||
function SneakySideEffects() {
|
||||
const editor = useEditor()
|
||||
|
||||
useEffect(() => {
|
||||
editor.sideEffects.registerAfterChangeHandler('shape', () => {
|
||||
editor.emit('update-drawings' as any)
|
||||
})
|
||||
editor.sideEffects.registerAfterCreateHandler('shape', () => {
|
||||
editor.emit('update-drawings' as any)
|
||||
})
|
||||
editor.sideEffects.registerAfterDeleteHandler('shape', () => {
|
||||
editor.emit('update-drawings' as any)
|
||||
})
|
||||
}, [editor])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
const LiveImageAssets = track(function LiveImageAssets() {
|
||||
const editor = useEditor()
|
||||
|
||||
return (
|
||||
<Inject selector=".tl-overlays .tl-html-layer">
|
||||
{editor
|
||||
.getCurrentPageShapes()
|
||||
.filter((shape): shape is LiveImageShape => shape.type === 'live-image')
|
||||
.map((shape) => (
|
||||
<LiveImageAsset key={shape.id} shape={shape} />
|
||||
))}
|
||||
</Inject>
|
||||
)
|
||||
})
|
||||
|
||||
const LiveImageAsset = track(function LiveImageAsset({ shape }: { shape: LiveImageShape }) {
|
||||
const editor = useEditor()
|
||||
|
||||
if (!shape.props.overlayResult) return null
|
||||
|
||||
const transform = editor.getShapePageTransform(shape).toCssString()
|
||||
const assetId = AssetRecordType.createId(shape.id.split(':')[1])
|
||||
const asset = editor.getAsset(assetId)
|
||||
return (
|
||||
asset &&
|
||||
asset.props.src && (
|
||||
<img
|
||||
src={asset.props.src!}
|
||||
alt={shape.props.name}
|
||||
width={shape.props.w}
|
||||
height={shape.props.h}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: shape.props.w,
|
||||
height: shape.props.h,
|
||||
maxWidth: 'none',
|
||||
transform,
|
||||
transformOrigin: 'top left',
|
||||
opacity: shape.opacity,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
function Inject({ children, selector }: { children: React.ReactNode; selector: string }) {
|
||||
const [parent, setParent] = useState<Element | null>(null)
|
||||
const target = useMemo(() => parent?.querySelector(selector) ?? null, [parent, selector])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div ref={(el) => setParent(el?.parentElement ?? null)} style={{ display: 'none' }} />
|
||||
{target && createPortal(children, target)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
Loading…
Reference in New Issue