Clean up tool names

This commit is contained in:
Jeff-Emmett 2025-01-28 16:38:41 +01:00
parent 86b37b9cc8
commit 9b33efdcb3
10 changed files with 426 additions and 401 deletions

View File

@ -25,7 +25,7 @@ import {
ClickPropagator, ClickPropagator,
} from "@/propagators/ScopedPropagators" } from "@/propagators/ScopedPropagators"
import { SlideShapeTool } from "@/tools/SlideShapeTool" import { SlideShapeTool } from "@/tools/SlideShapeTool"
import { SlideShapeUtil } from "@/shapes/SlideShapeUtil" import { SlideShape } from "@/shapes/SlideShapeUtil"
import { makeRealSettings, applySettingsMigrations } from "@/lib/settings" import { makeRealSettings, applySettingsMigrations } from "@/lib/settings"
import { PromptShapeTool } from "@/tools/PromptShapeTool" import { PromptShapeTool } from "@/tools/PromptShapeTool"
import { PromptShape } from "@/shapes/PromptShapeUtil" import { PromptShape } from "@/shapes/PromptShapeUtil"
@ -38,7 +38,7 @@ const customShapeUtils = [
ChatBoxShape, ChatBoxShape,
VideoChatShape, VideoChatShape,
EmbedShape, EmbedShape,
SlideShapeUtil, SlideShape,
MycrozineTemplateShape, MycrozineTemplateShape,
MarkdownShape, MarkdownShape,
PromptShape, PromptShape,

View File

@ -1,165 +1,168 @@
import { import {
BaseBoxShapeUtil, BaseBoxShapeUtil,
HTMLContainer, HTMLContainer,
TLBaseShape, TLBaseShape,
TLGeoShape, TLGeoShape,
TLShape, TLShape,
} from "tldraw" } from "tldraw"
import { getEdge } from "@/propagators/tlgraph" import { getEdge } from "@/propagators/tlgraph"
import { llm } from "@/utils/llm" import { llm } from "@/utils/llm"
import { isShapeOfType } from "@/propagators/utils" import { isShapeOfType } from "@/propagators/utils"
type IPrompt = TLBaseShape< type IPrompt = TLBaseShape<
"prompt", "Prompt",
{ {
w: number w: number
h: number h: number
prompt: string prompt: string
value: string value: string
agentBinding: string | null agentBinding: string | null
} }
> >
export class PromptShape extends BaseBoxShapeUtil<IPrompt> { export class PromptShape extends BaseBoxShapeUtil<IPrompt> {
static override type = "prompt" as const static override type = "Prompt" as const
FIXED_HEIGHT = 50 as const FIXED_HEIGHT = 50 as const
MIN_WIDTH = 150 as const MIN_WIDTH = 150 as const
PADDING = 4 as const PADDING = 4 as const
getDefaultProps(): IPrompt["props"] { getDefaultProps(): IPrompt["props"] {
return { return {
w: 300, w: 300,
h: 50, h: 50,
prompt: "", prompt: "",
value: "", value: "",
agentBinding: null, agentBinding: null,
} }
} }
// override onResize: TLResizeHandle<IPrompt> = ( // override onResize: TLResizeHandle<IPrompt> = (
// shape, // shape,
// { scaleX, initialShape }, // { scaleX, initialShape },
// ) => { // ) => {
// const { x, y } = shape // const { x, y } = shape
// const w = initialShape.props.w * scaleX // const w = initialShape.props.w * scaleX
// return { // return {
// x, // x,
// y, // y,
// props: { // props: {
// ...shape.props, // ...shape.props,
// w: Math.max(Math.abs(w), this.MIN_WIDTH), // w: Math.max(Math.abs(w), this.MIN_WIDTH),
// h: this.FIXED_HEIGHT, // h: this.FIXED_HEIGHT,
// }, // },
// } // }
// } // }
component(shape: IPrompt) { component(shape: IPrompt) {
const arrowBindings = this.editor.getBindingsInvolvingShape( const arrowBindings = this.editor.getBindingsInvolvingShape(
shape.id, shape.id,
"arrow", "arrow",
) )
const arrows = arrowBindings const arrows = arrowBindings.map((binding) =>
.map((binding) => this.editor.getShape(binding.fromId)) this.editor.getShape(binding.fromId),
)
const inputMap = arrows.reduce((acc, arrow) => { const inputMap = arrows.reduce((acc, arrow) => {
const edge = getEdge(arrow, this.editor); const edge = getEdge(arrow, this.editor)
if (edge) { if (edge) {
const sourceShape = this.editor.getShape(edge.from); const sourceShape = this.editor.getShape(edge.from)
if (sourceShape && edge.text) { if (sourceShape && edge.text) {
acc[edge.text] = sourceShape; acc[edge.text] = sourceShape
} }
} }
return acc; return acc
}, {} as Record<string, TLShape>); }, {} as Record<string, TLShape>)
const generateText = async (prompt: string) => { const generateText = async (prompt: string) => {
await llm('', prompt, (partial: string, done: boolean) => { await llm("", prompt, (partial: string, done: boolean) => {
console.log("DONE??", done) console.log("DONE??", done)
this.editor.updateShape<IPrompt>({ this.editor.updateShape<IPrompt>({
id: shape.id, id: shape.id,
type: "prompt", type: "Prompt",
props: { value: partial, agentBinding: done ? null : 'someone' }, props: { value: partial, agentBinding: done ? null : "someone" },
}) })
}) })
} }
const handlePrompt = () => { const handlePrompt = () => {
if (shape.props.agentBinding) { if (shape.props.agentBinding) {
return return
} }
let processedPrompt = shape.props.prompt; let processedPrompt = shape.props.prompt
for (const [key, sourceShape] of Object.entries(inputMap)) { for (const [key, sourceShape] of Object.entries(inputMap)) {
const pattern = `{${key}}`; const pattern = `{${key}}`
if (processedPrompt.includes(pattern)) { if (processedPrompt.includes(pattern)) {
if (isShapeOfType<TLGeoShape>(sourceShape, 'geo')) { if (isShapeOfType<TLGeoShape>(sourceShape, "geo")) {
processedPrompt = processedPrompt.replace(pattern, sourceShape.props.text); processedPrompt = processedPrompt.replace(
} pattern,
} sourceShape.props.text,
} )
console.log(processedPrompt); }
generateText(processedPrompt) }
}; }
//console.log(processedPrompt)
generateText(processedPrompt)
}
return ( return (
<HTMLContainer <HTMLContainer
style={{ style={{
borderRadius: 6, borderRadius: 6,
border: "1px solid lightgrey", border: "1px solid lightgrey",
padding: this.PADDING, padding: this.PADDING,
height: this.FIXED_HEIGHT, height: this.FIXED_HEIGHT,
width: shape.props.w, width: shape.props.w,
pointerEvents: "all", pointerEvents: "all",
backgroundColor: "#efefef", backgroundColor: "#efefef",
overflow: "visible", overflow: "visible",
display: "flex", display: "flex",
justifyContent: "center", justifyContent: "center",
alignItems: "center", alignItems: "center",
outline: shape.props.agentBinding ? "2px solid orange" : "none", outline: shape.props.agentBinding ? "2px solid orange" : "none",
}} }}
> >
<input <input
style={{ style={{
width: "100%", width: "100%",
height: "100%", height: "100%",
overflow: "visible", overflow: "visible",
backgroundColor: "rgba(0, 0, 0, 0.05)", backgroundColor: "rgba(0, 0, 0, 0.05)",
border: "1px solid rgba(0, 0, 0, 0.05)", border: "1px solid rgba(0, 0, 0, 0.05)",
borderRadius: 6 - this.PADDING, borderRadius: 6 - this.PADDING,
fontSize: 16, fontSize: 16,
}} }}
type="text" type="text"
placeholder="Enter prompt..." placeholder="Enter prompt..."
value={shape.props.prompt} value={shape.props.prompt}
onChange={(text) => { onChange={(text) => {
this.editor.updateShape<IPrompt>({ this.editor.updateShape<IPrompt>({
id: shape.id, id: shape.id,
type: "prompt", type: "Prompt",
props: { prompt: text.target.value }, props: { prompt: text.target.value },
}) })
}} }}
/> />
<button <button
style={{ style={{
width: 100, width: 100,
height: "100%", height: "100%",
marginLeft: 5, marginLeft: 5,
pointerEvents: "all", pointerEvents: "all",
}} }}
onPointerDown={(e) => { onPointerDown={(e) => {
e.stopPropagation() e.stopPropagation()
}} }}
type="button" type="button"
onClick={handlePrompt} onClick={handlePrompt}
> >
Prompt Prompt
</button> </button>
</HTMLContainer> </HTMLContainer>
) )
} }
// [5] indicator(shape: IPrompt) {
indicator(shape: IPrompt) { return <rect width={shape.props.w} height={shape.props.h} rx={5} />
return <rect width={shape.props.w} height={shape.props.h} rx={5} /> }
} }
}

View File

@ -1,128 +1,136 @@
import { useCallback } from 'react' import { useCallback } from "react"
import { import {
BaseBoxShapeUtil, BaseBoxShapeUtil,
Geometry2d, Geometry2d,
RecordProps, RecordProps,
Rectangle2d, Rectangle2d,
SVGContainer, SVGContainer,
ShapeUtil, ShapeUtil,
T, T,
TLBaseShape, TLBaseShape,
getPerfectDashProps, getPerfectDashProps,
resizeBox, resizeBox,
useValue, useValue,
} from 'tldraw' } from "tldraw"
import { moveToSlide, useSlides } from '@/slides/useSlides' import { moveToSlide, useSlides } from "@/slides/useSlides"
export type ISlideShape = TLBaseShape< export type ISlideShape = TLBaseShape<
'Slide', "Slide",
{ {
w: number w: number
h: number h: number
} }
> >
export class SlideShapeUtil extends BaseBoxShapeUtil<ISlideShape> { export class SlideShape extends BaseBoxShapeUtil<ISlideShape> {
static override type = "Slide" static override type = "Slide"
// static override props = {
// w: T.number,
// h: T.number,
// }
override canBind = () => false // static override props = {
override hideRotateHandle = () => true // w: T.number,
// h: T.number,
// }
getDefaultProps(): ISlideShape["props"] { override canBind = () => false
return { override hideRotateHandle = () => true
w: 720,
h: 480,
}
}
getGeometry(shape: ISlideShape): Geometry2d { getDefaultProps(): ISlideShape["props"] {
return new Rectangle2d({ return {
width: shape.props.w, w: 720,
height: shape.props.h, h: 480,
isFilled: false, }
}) }
}
override onRotate = (initial: ISlideShape) => initial getGeometry(shape: ISlideShape): Geometry2d {
override onResize(shape: ISlideShape, info: any) { return new Rectangle2d({
return resizeBox(shape, info) width: shape.props.w,
} height: shape.props.h,
isFilled: false,
})
}
override onDoubleClick = (shape: ISlideShape) => { override onRotate = (initial: ISlideShape) => initial
moveToSlide(this.editor, shape) override onResize(shape: ISlideShape, info: any) {
this.editor.selectNone() return resizeBox(shape, info)
} }
override onDoubleClickEdge = (shape: ISlideShape) => { override onDoubleClick = (shape: ISlideShape) => {
moveToSlide(this.editor, shape) moveToSlide(this.editor, shape)
this.editor.selectNone() this.editor.selectNone()
} }
component(shape: ISlideShape) { override onDoubleClickEdge = (shape: ISlideShape) => {
const bounds = this.editor.getShapeGeometry(shape).bounds moveToSlide(this.editor, shape)
this.editor.selectNone()
}
// eslint-disable-next-line react-hooks/rules-of-hooks component(shape: ISlideShape) {
const zoomLevel = useValue('zoom level', () => this.editor.getZoomLevel(), [this.editor]) const bounds = this.editor.getShapeGeometry(shape).bounds
// eslint-disable-next-line react-hooks/rules-of-hooks // eslint-disable-next-line react-hooks/rules-of-hooks
const slides = useSlides() const zoomLevel = useValue("zoom level", () => this.editor.getZoomLevel(), [
const index = slides.findIndex((s) => s.id === shape.id) this.editor,
])
// eslint-disable-next-line react-hooks/rules-of-hooks // eslint-disable-next-line react-hooks/rules-of-hooks
const handleLabelPointerDown = useCallback(() => this.editor.select(shape.id), [shape.id]) const slides = useSlides()
const index = slides.findIndex((s) => s.id === shape.id)
if (!bounds) return null // eslint-disable-next-line react-hooks/rules-of-hooks
const handleLabelPointerDown = useCallback(
() => this.editor.select(shape.id),
[shape.id],
)
return ( if (!bounds) return null
<>
<div onPointerDown={handleLabelPointerDown} className="slide-shape-label">
{`Slide ${index + 1}`}
</div>
<SVGContainer>
<g
style={{
stroke: 'var(--color-text)',
strokeWidth: 'calc(1px * var(--tl-scale))',
opacity: 0.25,
}}
pointerEvents="none"
strokeLinecap="round"
strokeLinejoin="round"
>
{bounds.sides.map((side, i) => {
const { strokeDasharray, strokeDashoffset } = getPerfectDashProps(
side[0].dist(side[1]),
1 / zoomLevel,
{
style: 'dashed',
lengthRatio: 6,
}
)
return ( return (
<line <>
key={i} <div
x1={side[0].x} onPointerDown={handleLabelPointerDown}
y1={side[0].y} className="slide-shape-label"
x2={side[1].x} >
y2={side[1].y} {`Slide ${index + 1}`}
strokeDasharray={strokeDasharray} </div>
strokeDashoffset={strokeDashoffset} <SVGContainer>
/> <g
) style={{
})} stroke: "var(--color-text)",
</g> strokeWidth: "calc(1px * var(--tl-scale))",
</SVGContainer> opacity: 0.25,
</> }}
) pointerEvents="none"
} strokeLinecap="round"
strokeLinejoin="round"
>
{bounds.sides.map((side, i) => {
const { strokeDasharray, strokeDashoffset } = getPerfectDashProps(
side[0].dist(side[1]),
1 / zoomLevel,
{
style: "dashed",
lengthRatio: 6,
},
)
indicator(shape: ISlideShape) { return (
return <rect width={shape.props.w} height={shape.props.h} /> <line
} key={i}
} x1={side[0].x}
y1={side[0].y}
x2={side[1].x}
y2={side[1].y}
strokeDasharray={strokeDasharray}
strokeDashoffset={strokeDashoffset}
/>
)
})}
</g>
</SVGContainer>
</>
)
}
indicator(shape: ISlideShape) {
return <rect width={shape.props.w} height={shape.props.h} />
}
}

View File

@ -1,8 +1,8 @@
import { BaseBoxShapeTool } from 'tldraw' import { BaseBoxShapeTool } from 'tldraw'
export class PromptShapeTool extends BaseBoxShapeTool { export class PromptShapeTool extends BaseBoxShapeTool {
static override id = 'prompt' static override id = 'Prompt'
static override initial = 'idle' static override initial = 'idle'
override shapeType = 'prompt' override shapeType = 'Prompt'
} }

View File

@ -7,6 +7,6 @@ export class SlideShapeTool extends BaseBoxShapeTool {
constructor(editor: any) { constructor(editor: any) {
super(editor) super(editor)
console.log('SlideShapeTool constructed', { id: this.id, shapeType: this.shapeType }) //console.log('SlideShapeTool constructed', { id: this.id, shapeType: this.shapeType })
} }
} }

View File

@ -142,7 +142,7 @@ export function CustomContextMenu(props: TLUiContextMenuProps) {
}} }}
/> />
<TldrawUiMenuItem <TldrawUiMenuItem
id="mycrozine-template" id="MycrozineTemplate"
label="Create Mycrozine Template" label="Create Mycrozine Template"
icon="rectangle" icon="rectangle"
kbd="m" kbd="m"
@ -152,7 +152,7 @@ export function CustomContextMenu(props: TLUiContextMenuProps) {
}} }}
/> />
<TldrawUiMenuItem <TldrawUiMenuItem
id="markdown" id="Markdown"
label="Create Markdown" label="Create Markdown"
icon="markdown" icon="markdown"
kbd="alt+m" kbd="alt+m"
@ -161,6 +161,16 @@ export function CustomContextMenu(props: TLUiContextMenuProps) {
editor.setCurrentTool("Markdown") editor.setCurrentTool("Markdown")
}} }}
/> />
<TldrawUiMenuItem
id="Prompt"
label="Create Prompt"
icon="prompt"
kbd="alt+p"
disabled={hasSelection}
onSelect={() => {
editor.setCurrentTool("Prompt")
}}
/>
</TldrawUiMenuGroup> </TldrawUiMenuGroup>
{/* Frame Controls */} {/* Frame Controls */}

View File

@ -3,11 +3,15 @@ import { DefaultToolbar, DefaultToolbarContent } from "tldraw"
import { useTools } from "tldraw" import { useTools } from "tldraw"
import { useEditor } from "tldraw" import { useEditor } from "tldraw"
import { useState, useEffect } from "react" import { useState, useEffect } from "react"
import { useDialogs } from "tldraw"
import { SettingsDialog } from "./SettingsDialog"
export function CustomToolbar() { export function CustomToolbar() {
const editor = useEditor() const editor = useEditor()
const tools = useTools() const tools = useTools()
const [isReady, setIsReady] = useState(false) const [isReady, setIsReady] = useState(false)
const [hasApiKey, setHasApiKey] = useState(false)
const { addDialog, removeDialog } = useDialogs()
useEffect(() => { useEffect(() => {
if (editor && tools) { if (editor && tools) {
@ -15,53 +19,132 @@ export function CustomToolbar() {
} }
}, [editor, tools]) }, [editor, tools])
useEffect(() => {
const settings = localStorage.getItem("jeff_keys")
if (settings) {
const { keys } = JSON.parse(settings)
setHasApiKey(Object.values(keys).some((key) => key))
}
}, [])
if (!isReady) return null if (!isReady) return null
return ( return (
<DefaultToolbar> <div style={{ position: "relative" }}>
<DefaultToolbarContent /> <div
{tools["VideoChat"] && ( style={{
<TldrawUiMenuItem position: "fixed",
{...tools["VideoChat"]} top: "40px",
icon="video" right: "12px",
label="Video Chat" zIndex: 99999,
isSelected={tools["VideoChat"].id === editor.getCurrentToolId()} pointerEvents: "auto",
/> display: "flex",
)} gap: "8px",
{tools["ChatBox"] && ( }}
<TldrawUiMenuItem >
{...tools["ChatBox"]} <button
icon="chat" onClick={() => {
label="Chat" addDialog({
isSelected={tools["ChatBox"].id === editor.getCurrentToolId()} id: "api-keys",
/> component: ({ onClose }: { onClose: () => void }) => (
)} <SettingsDialog
{tools["Embed"] && ( onClose={() => {
<TldrawUiMenuItem onClose()
{...tools["Embed"]} removeDialog("api-keys")
icon="embed" const settings = localStorage.getItem("jeff_keys")
label="Embed" if (settings) {
isSelected={tools["Embed"].id === editor.getCurrentToolId()} const { keys } = JSON.parse(settings)
/> setHasApiKey(Object.values(keys).some((key) => key))
)} }
{tools["SlideShape"] && ( }}
<TldrawUiMenuItem />
{...tools["SlideShape"]} ),
icon="slides" })
label="Slide" }}
isSelected={tools["SlideShape"].id === editor.getCurrentToolId()} style={{
/> padding: "8px 16px",
)} borderRadius: "4px",
{/* background: "#2F80ED",
{tools["Markdown"] && ( color: "white",
<TldrawUiMenuItem border: "none",
{...tools["Markdown"]} cursor: "pointer",
icon="markdown" fontWeight: 500,
label="Markdown" transition: "background 0.2s ease",
isSelected={tools["Markdown"].id === editor.getCurrentToolId()} boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
/> whiteSpace: "nowrap",
)} userSelect: "none",
*/} }}
</DefaultToolbar> onMouseEnter={(e) => {
e.currentTarget.style.background = "#1366D6"
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = "#2F80ED"
}}
>
Keys {hasApiKey ? "✅" : "❌"}
</button>
</div>
<DefaultToolbar>
<DefaultToolbarContent />
{tools["VideoChat"] && (
<TldrawUiMenuItem
{...tools["VideoChat"]}
icon="video"
label="Video Chat"
isSelected={tools["VideoChat"].id === editor.getCurrentToolId()}
/>
)}
{tools["ChatBox"] && (
<TldrawUiMenuItem
{...tools["ChatBox"]}
icon="chat"
label="Chat"
isSelected={tools["ChatBox"].id === editor.getCurrentToolId()}
/>
)}
{tools["Embed"] && (
<TldrawUiMenuItem
{...tools["Embed"]}
icon="embed"
label="Embed"
isSelected={tools["Embed"].id === editor.getCurrentToolId()}
/>
)}
{tools["SlideShape"] && (
<TldrawUiMenuItem
{...tools["SlideShape"]}
icon="slides"
label="Slide"
isSelected={tools["SlideShape"].id === editor.getCurrentToolId()}
/>
)}
{tools["Markdown"] && (
<TldrawUiMenuItem
{...tools["Markdown"]}
icon="markdown"
label="Markdown"
isSelected={tools["Markdown"].id === editor.getCurrentToolId()}
/>
)}
{tools["MycrozineTemplate"] && (
<TldrawUiMenuItem
{...tools["MycrozineTemplate"]}
icon="mycrozinetemplate"
label="MycrozineTemplate"
isSelected={
tools["MycrozineTemplate"].id === editor.getCurrentToolId()
}
/>
)}
{tools["Prompt"] && (
<TldrawUiMenuItem
{...tools["Prompt"]}
icon="prompt"
label="Prompt"
isSelected={tools["Prompt"].id === editor.getCurrentToolId()}
/>
)}
</DefaultToolbar>
</div>
) )
} }

View File

@ -4,102 +4,17 @@ import { CustomContextMenu } from "./CustomContextMenu"
import { import {
DefaultKeyboardShortcutsDialog, DefaultKeyboardShortcutsDialog,
DefaultKeyboardShortcutsDialogContent, DefaultKeyboardShortcutsDialogContent,
DefaultToolbar,
DefaultToolbarContent,
TLComponents, TLComponents,
TldrawUiMenuItem, TldrawUiMenuItem,
useDialogs,
useIsToolSelected,
useTools, useTools,
} from "tldraw" } from "tldraw"
import { SettingsDialog } from "./SettingsDialog"
import { useEffect } from "react"
import { SlidesPanel } from "@/slides/SlidesPanel" import { SlidesPanel } from "@/slides/SlidesPanel"
import { useState } from "react"
export const components: TLComponents = { export const components: TLComponents = {
// Toolbar: CustomToolbar, Toolbar: CustomToolbar,
MainMenu: CustomMainMenu, MainMenu: CustomMainMenu,
ContextMenu: CustomContextMenu, ContextMenu: CustomContextMenu,
HelperButtons: SlidesPanel, HelperButtons: SlidesPanel,
Toolbar: (props: any) => {
const tools = useTools()
const slideTool = tools["Slide"]
const isSlideSelected = slideTool ? useIsToolSelected(slideTool) : false
const { addDialog, removeDialog } = useDialogs()
const [hasApiKey, setHasApiKey] = useState(false)
useEffect(() => {
const key = localStorage.getItem("openai_api_key")
setHasApiKey(!!key)
}, [])
return (
<div style={{ position: "relative", width: "100%", height: "100%" }}>
<div
style={{
position: "fixed",
top: "40px",
right: "12px",
zIndex: 99999,
pointerEvents: "auto",
display: "flex",
gap: "8px",
}}
>
(
<button
onClick={() => {
addDialog({
id: "api-keys",
component: ({ onClose }: { onClose: () => void }) => (
<SettingsDialog
onClose={() => {
onClose()
removeDialog("api-keys")
const settings = localStorage.getItem("jeff_keys")
if (settings) {
const { keys } = JSON.parse(settings)
setHasApiKey(Object.values(keys).some((key) => key))
}
}}
/>
),
})
}}
style={{
padding: "8px 16px",
borderRadius: "4px",
background: "#2F80ED",
color: "white",
border: "none",
cursor: "pointer",
fontWeight: 500,
transition: "background 0.2s ease",
boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
whiteSpace: "nowrap",
userSelect: "none",
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = "#1366D6"
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = "#2F80ED"
}}
>
Keys {hasApiKey ? "✅" : "❌"}
</button>
)
</div>
<DefaultToolbar {...props}>
{slideTool && (
<TldrawUiMenuItem {...slideTool} isSelected={isSlideSelected} />
)}
<DefaultToolbarContent />
</DefaultToolbar>
</div>
)
},
KeyboardShortcutsDialog: (props: any) => { KeyboardShortcutsDialog: (props: any) => {
const tools = useTools() const tools = useTools()
return ( return (

View File

@ -106,10 +106,7 @@ export const overrides: TLUiOverrides = {
type: "Slide", type: "Slide",
readonlyOk: true, readonlyOk: true,
onSelect: () => { onSelect: () => {
console.log("SlideShape tool selected from menu")
console.log("Current tool before:", editor.getCurrentToolId())
editor.setCurrentTool("Slide") editor.setCurrentTool("Slide")
console.log("Current tool after:", editor.getCurrentToolId())
}, },
}, },
Markdown: { Markdown: {
@ -130,6 +127,15 @@ export const overrides: TLUiOverrides = {
readonlyOk: true, readonlyOk: true,
onSelect: () => editor.setCurrentTool("MycrozineTemplate"), onSelect: () => editor.setCurrentTool("MycrozineTemplate"),
}, },
Prompt: {
id: "Prompt",
icon: "prompt",
label: "Prompt",
type: "Prompt",
kdb: "p",
readonlyOk: true,
onSelect: () => editor.setCurrentTool("Prompt"),
},
hand: { hand: {
...tools.hand, ...tools.hand,
onDoubleClick: (info: any) => { onDoubleClick: (info: any) => {

View File

@ -17,8 +17,7 @@ import { VideoChatShape } from "@/shapes/VideoChatShapeUtil"
import { EmbedShape } from "@/shapes/EmbedShapeUtil" import { EmbedShape } from "@/shapes/EmbedShapeUtil"
import { MarkdownShape } from "@/shapes/MarkdownShapeUtil" import { MarkdownShape } from "@/shapes/MarkdownShapeUtil"
import { MycrozineTemplateShape } from "@/shapes/MycrozineTemplateShapeUtil" import { MycrozineTemplateShape } from "@/shapes/MycrozineTemplateShapeUtil"
import { T } from "@tldraw/tldraw" import { SlideShape } from "@/shapes/SlideShapeUtil"
import { SlideShapeUtil } from "@/shapes/SlideShapeUtil"
// add custom shapes and bindings here if needed: // add custom shapes and bindings here if needed:
export const customSchema = createTLSchema({ export const customSchema = createTLSchema({
@ -45,8 +44,8 @@ export const customSchema = createTLSchema({
migrations: MycrozineTemplateShape.migrations, migrations: MycrozineTemplateShape.migrations,
}, },
Slide: { Slide: {
props: SlideShapeUtil.props, props: SlideShape.props,
migrations: SlideShapeUtil.migrations, migrations: SlideShape.migrations,
}, },
}, },
bindings: defaultBindingSchemas, bindings: defaultBindingSchemas,
@ -225,6 +224,7 @@ export class TldrawDurableObject {
onDataChange: () => { onDataChange: () => {
// and persist whenever the data in the room changes // and persist whenever the data in the room changes
this.schedulePersistToR2() this.schedulePersistToR2()
console.log("Persisting", this.roomId, "to R2")
}, },
}) })
})() })()
@ -237,7 +237,7 @@ export class TldrawDurableObject {
schedulePersistToR2 = throttle(async () => { schedulePersistToR2 = throttle(async () => {
if (!this.roomPromise || !this.roomId) return if (!this.roomPromise || !this.roomId) return
const room = await this.getRoom() const room = await this.getRoom()
// convert the room to JSON and upload it to R2 // convert the room to JSON and upload it to R2
const snapshot = JSON.stringify(room.getCurrentSnapshot()) const snapshot = JSON.stringify(room.getCurrentSnapshot())
await this.r2.put(`rooms/${this.roomId}`, snapshot) await this.r2.put(`rooms/${this.roomId}`, snapshot)