diff --git a/app/CodeEditor/CodeEditor.tsx b/app/CodeEditor/CodeEditor.tsx new file mode 100644 index 00000000..f91fbabd --- /dev/null +++ b/app/CodeEditor/CodeEditor.tsx @@ -0,0 +1,117 @@ +import { Editor as MonacoEditor, OnChange } from '@monaco-editor/react' +import { stopEventPropagation, track, useEditor, useIsDarkMode } from '@tldraw/tldraw' +import { useState } from 'react' +import { PreviewShape, showingEditor } from '../PreviewShape/PreviewShape' +import { updateLink } from '../lib/uploadLink' + +export const EDITOR_WIDTH = 700 + +export const CodeEditor = track(() => { + const editor = useEditor() + const dark = useIsDarkMode() + const bounds = editor.getViewportPageBounds() + const shape = editor.getOnlySelectedShape() + const previewShape = shape?.type === 'preview' ? (shape as PreviewShape) : undefined + + const [value, setValue] = useState('') + const [isSaving, setIsSaving] = useState(false) + const showEditor = showingEditor.get() + + const handleOnChange: OnChange = (value) => { + setValue(value) + } + + if (!bounds || !previewShape || !showEditor) return null + + return ( + <> +
stopEventPropagation(e)} + onKeyUp={async (e) => { + if (e.key === 's' && e.ctrlKey) { + setIsSaving(true) + if (!value && value === '') return + await updateLink(shape.id, value) + editor.updateShape({ + id: previewShape.id, + type: 'preview', + props: { + html: value, + linkUploadVersion: previewShape.props.linkUploadVersion + 1, + }, + }) + setIsSaving(false) + } + }} + > +
+
+ + +
+
+ +
+
+
+ + ) +}) diff --git a/app/PreviewShape/PreviewShape.tsx b/app/PreviewShape/PreviewShape.tsx index 719a86c4..7d910f50 100644 --- a/app/PreviewShape/PreviewShape.tsx +++ b/app/PreviewShape/PreviewShape.tsx @@ -1,22 +1,22 @@ /* eslint-disable react-hooks/rules-of-hooks */ import { - TLBaseShape, BaseBoxShapeUtil, - useIsEditing, - HTMLContainer, - toDomPrecision, - Icon, - useToasts, DefaultSpinner, - stopEventPropagation, + HTMLContainer, + TLBaseShape, Vec2d, + atom, + toDomPrecision, + useIsEditing, useValue, } from '@tldraw/tldraw' +import { useEffect } from 'react' +import { CopyToClipboardButton } from '../components/CopyToClipboardButton' +import { Hint } from '../components/Hint' +import { ShowEditorButton, showShapeNextToEditor } from '../components/ShowEditorButton' import { UrlLinkButton } from '../components/UrlLinkButton' import { LINK_HOST, PROTOCOL } from '../lib/hosts' -import { useEffect } from 'react' import { uploadLink } from '../lib/uploadLink' -import style from 'styled-jsx/style' export type PreviewShape = TLBaseShape< 'preview', @@ -30,6 +30,8 @@ export type PreviewShape = TLBaseShape< } > +export const showingEditor = atom('showingEditor', false) + export class PreviewShapeUtil extends BaseBoxShapeUtil { static override type = 'preview' as const @@ -50,7 +52,6 @@ export class PreviewShapeUtil extends BaseBoxShapeUtil { override component(shape: PreviewShape) { const isEditing = useIsEditing(shape.id) - const toast = useToasts() const boxShadow = useValue( 'box shadow', @@ -86,7 +87,6 @@ export class PreviewShapeUtil extends BaseBoxShapeUtil { } }, [shape.id, html, linkUploadVersion, uploadedShapeId]) - console.log(shape, uploadedShapeId) const isLoading = linkUploadVersion === undefined || uploadedShapeId !== shape.id const uploadUrl = [PROTOCOL, LINK_HOST, '/', shape.id.replace(/^shape:/, '')].join('') @@ -123,35 +123,17 @@ export class PreviewShapeUtil extends BaseBoxShapeUtil { borderRadius: 'var(--radius-2)', }} /> - - + + + +
{ {isEditing ? 'Click the canvas to exit' : 'Double click to interact'}
+ )} ) } + override onClick = (shape: PreviewShape) => { + if (!showingEditor.get()) return + showShapeNextToEditor(this.editor, shape) + } + indicator(shape: PreviewShape) { return } diff --git a/app/components/CopyToClipboardButton.tsx b/app/components/CopyToClipboardButton.tsx new file mode 100644 index 00000000..34bb8ea3 --- /dev/null +++ b/app/components/CopyToClipboardButton.tsx @@ -0,0 +1,33 @@ +import { Icon, stopEventPropagation, useToasts } from '@tldraw/tldraw' +import { PreviewShape } from '../PreviewShape/PreviewShape' + +export function CopyToClipboardButton({ shape }: { shape: PreviewShape }) { + const toast = useToasts() + return ( + + ) +} diff --git a/app/components/Hint.tsx b/app/components/Hint.tsx new file mode 100644 index 00000000..13295099 --- /dev/null +++ b/app/components/Hint.tsx @@ -0,0 +1,31 @@ +export function Hint({ isEditing }: { isEditing: boolean }) { + return ( +
+ + {isEditing ? 'Click the canvas to exit' : 'Double click to interact'} + +
+ ) +} diff --git a/app/components/ShowEditorButton.tsx b/app/components/ShowEditorButton.tsx new file mode 100644 index 00000000..233ec5bb --- /dev/null +++ b/app/components/ShowEditorButton.tsx @@ -0,0 +1,45 @@ +'use client' +import { Editor, Icon, TLShape, stopEventPropagation, track, useEditor } from '@tldraw/tldraw' +import { PreviewShape, showingEditor } from '../PreviewShape/PreviewShape' +import { EDITOR_WIDTH } from '../CodeEditor/CodeEditor' + +export const ShowEditorButton = track(({ shape }: { shape: PreviewShape }) => { + const showing = showingEditor.get() + const editor = useEditor() + return ( + + ) +}) + +export function showShapeNextToEditor(editor: Editor, shape: TLShape) { + const bounds = editor.getViewportPageBounds() + editor.centerOnPoint( + { + x: shape.x + bounds.width / 2 - (EDITOR_WIDTH + 40) / editor.getZoomLevel(), + y: shape.y + bounds.height / 2 - 20 / editor.getZoomLevel(), + }, + { duration: 320 } + ) +} diff --git a/app/components/ShowResult.tsx b/app/components/ShowResult.tsx new file mode 100644 index 00000000..fb2a44f7 --- /dev/null +++ b/app/components/ShowResult.tsx @@ -0,0 +1,30 @@ +import { toDomPrecision } from '@tldraw/tldraw' +import { PreviewShape } from '../PreviewShape/PreviewShape' + +export function ShowResult({ + boxShadow, + isEditing, + html, + shape, +}: { + boxShadow: string + isEditing: boolean + html: string + shape: PreviewShape +}) { + return ( +