Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add monaco editor and show it on click. #21

Open
wants to merge 32 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
63414b7
Add monaco editor and show it on click.
MitjaBezensek Nov 20, 2023
4c325d3
Merge branch 'main' into mitja/add-code-editor
MitjaBezensek Nov 21, 2023
88e81e8
Refactor.
MitjaBezensek Nov 21, 2023
3856c72
Fix merge conflict.
MitjaBezensek Nov 21, 2023
ff3c6e0
Remove stray paren.
MitjaBezensek Nov 21, 2023
2cab2ba
Only add the hack once.
MitjaBezensek Nov 21, 2023
bd99024
Add the editor below.
MitjaBezensek Nov 21, 2023
8cee627
Show the editor to the right.
MitjaBezensek Nov 21, 2023
ce253f6
Add editor infront of canvas.
MitjaBezensek Nov 22, 2023
1f845d9
Merge branch 'main' into mitja/add-code-editor
MitjaBezensek Nov 22, 2023
f8515f1
Fix imports.
MitjaBezensek Nov 22, 2023
d1b0ded
Update preview shape.
MitjaBezensek Nov 22, 2023
c60efa2
Fix buttons.
MitjaBezensek Nov 22, 2023
340dda3
Remove unused variable.
MitjaBezensek Nov 22, 2023
5bfe4af
Remove unused imports.
MitjaBezensek Nov 22, 2023
762cb5b
Send updates to verce.
MitjaBezensek Nov 22, 2023
3b37765
Improve button, remove consoles.
MitjaBezensek Nov 22, 2023
6dc4c15
Show saving indicator. Remove scrollbar.
MitjaBezensek Nov 22, 2023
5c4a539
Update appearance.
MitjaBezensek Nov 22, 2023
1baabce
Remove unused.
MitjaBezensek Nov 22, 2023
1928745
Move the camera.
MitjaBezensek Nov 23, 2023
9dccab6
Refactor.
MitjaBezensek Nov 23, 2023
9383dda
Fix selections.
MitjaBezensek Nov 23, 2023
d8f0b72
Dont show current line selection.
MitjaBezensek Nov 23, 2023
d876337
Remove unused import.
MitjaBezensek Nov 23, 2023
e23cbce
Show different icon when showing editor.
MitjaBezensek Nov 23, 2023
360ff4d
Refactor.
MitjaBezensek Nov 23, 2023
3b48024
Remove unused import.
MitjaBezensek Nov 23, 2023
81b6f65
Imporve ux.
MitjaBezensek Nov 23, 2023
7129f47
Add focus mode.
MitjaBezensek Nov 24, 2023
2f7a84b
Remove unused variable.
MitjaBezensek Nov 24, 2023
2000bc7
Remove console.log.
MitjaBezensek Nov 27, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions app/CodeEditor/CodeEditor.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<>
<div
style={{
position: 'absolute',
top: 20,
left: 20,
border: '1px solid #eee',
pointerEvents: 'all',
}}
onPointerDown={(e) => stopEventPropagation(e)}
onKeyUp={async (e) => {
if (e.key === 's' && e.ctrlKey) {
setIsSaving(true)
if (!value && value === '') return
await updateLink(shape.id, value)
editor.updateShape<PreviewShape>({
id: previewShape.id,
type: 'preview',
props: {
html: value,
linkUploadVersion: previewShape.props.linkUploadVersion + 1,
},
})
setIsSaving(false)
}
}}
>
<div style={{ display: 'flex', flexDirection: 'column' }}>
<div className="absolute bottom-2.5 right-2.5 flex gap-2.5">
<button
onPointerDown={stopEventPropagation}
onClick={(e) => {
stopEventPropagation(e)
showingEditor.set(false)
}}
className="bg-white hover:bg-gray-100 text-black border w-[80px] py-2 px-4 rounded box-border z-10 h-9"
style={{
pointerEvents: 'all',
}}
>
Dismiss
</button>
<button
className="z-10 bg-blue-500 hover:bg-blue-700 w-[80px] h-9 box-border text-white py-2 px-4 rounded"
onClick={async () => {
if (!value && value === '') return
await updateLink(shape.id, value)
editor.updateShape<PreviewShape>({
id: previewShape.id,
type: 'preview',
props: {
html: value,
linkUploadVersion: previewShape.props.linkUploadVersion + 1,
},
})
}}
>
{isSaving ? 'Saving...' : 'Save'}
</button>
</div>
<div style={{ width: EDITOR_WIDTH, height: 700 }}>
<MonacoEditor
defaultLanguage="html"
value={previewShape.props.html}
onChange={handleOnChange}
theme={dark ? 'vs-dark' : 'vs-light'}
options={{
renderLineHighlight: 'none',
overviewRulerBorder: false,
overviewRulerLanes: 0,
padding: {
top: 16,
},
minimap: {
enabled: false,
},
lineNumbers: 'off',
scrollbar: {
vertical: 'hidden',
},
wordWrap: 'wordWrapColumn',
wordWrapColumn: 80,
fontSize: 13,
}}
/>
</div>
</div>
</div>
</>
)
})
58 changes: 23 additions & 35 deletions app/PreviewShape/PreviewShape.tsx
Original file line number Diff line number Diff line change
@@ -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',
Expand All @@ -30,6 +30,8 @@ export type PreviewShape = TLBaseShape<
}
>

export const showingEditor = atom('showingEditor', false)

export class PreviewShapeUtil extends BaseBoxShapeUtil<PreviewShape> {
static override type = 'preview' as const

Expand All @@ -50,7 +52,6 @@ export class PreviewShapeUtil extends BaseBoxShapeUtil<PreviewShape> {

override component(shape: PreviewShape) {
const isEditing = useIsEditing(shape.id)
const toast = useToasts()

const boxShadow = useValue(
'box shadow',
Expand Down Expand Up @@ -86,7 +87,6 @@ export class PreviewShapeUtil extends BaseBoxShapeUtil<PreviewShape> {
}
}, [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('')
Expand Down Expand Up @@ -123,35 +123,17 @@ export class PreviewShapeUtil extends BaseBoxShapeUtil<PreviewShape> {
borderRadius: 'var(--radius-2)',
}}
/>
<button
<div
style={{
all: 'unset',
position: 'absolute',
top: 0,
right: -40,
height: 40,
width: 40,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
pointerEvents: 'all',
}}
onClick={() => {
if (navigator && navigator.clipboard) {
navigator.clipboard.writeText(shape.props.html)
toast.addToast({
icon: 'code',
title: 'Copied html to clipboard',
})
}
top: 0,
}}
onPointerDown={stopEventPropagation}
title="Copy code to clipboard"
>
<Icon icon="code" />
</button>
<UrlLinkButton uploadUrl={uploadUrl} />
<CopyToClipboardButton shape={shape} />
<UrlLinkButton uploadUrl={uploadUrl} />
<ShowEditorButton shape={shape} />
</div>
<div
style={{
textAlign: 'center',
Expand Down Expand Up @@ -179,12 +161,18 @@ export class PreviewShapeUtil extends BaseBoxShapeUtil<PreviewShape> {
{isEditing ? 'Click the canvas to exit' : 'Double click to interact'}
</span>
</div>
<Hint isEditing={isEditing} />
</>
)}
</HTMLContainer>
)
}

override onClick = (shape: PreviewShape) => {
if (!showingEditor.get()) return
showShapeNextToEditor(this.editor, shape)
}

indicator(shape: PreviewShape) {
return <rect width={shape.props.w} height={shape.props.h} />
}
Expand Down
33 changes: 33 additions & 0 deletions app/components/CopyToClipboardButton.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<button
style={{
all: 'unset',
height: 40,
width: 40,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
pointerEvents: 'all',
}}
onClick={() => {
if (navigator && navigator.clipboard) {
navigator.clipboard.writeText(shape.props.html)
toast.addToast({
icon: 'code',
title: 'Copied html to clipboard',
})
}
}}
onPointerDown={stopEventPropagation}
title="Copy code to clipboard"
>
<Icon icon="code" />
</button>
)
}
31 changes: 31 additions & 0 deletions app/components/Hint.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
export function Hint({ isEditing }: { isEditing: boolean }) {
return (
<div
style={{
textAlign: 'center',
position: 'absolute',
bottom: isEditing ? -40 : 0,
padding: 4,
fontFamily: 'inherit',
fontSize: 12,
left: 0,
width: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
pointerEvents: 'none',
}}
>
<span
style={{
background: 'var(--color-panel)',
padding: '4px 12px',
borderRadius: 99,
border: '1px solid var(--color-muted-1)',
}}
>
{isEditing ? 'Click the canvas to exit' : 'Double click to interact'}
</span>
</div>
)
}
45 changes: 45 additions & 0 deletions app/components/ShowEditorButton.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<button
style={{
all: 'unset',
height: 40,
width: 40,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
pointerEvents: 'all',
}}
onClick={() => {
editor.setSelectedShapes([shape.id])
if (!showing) {
showShapeNextToEditor(editor, shape)
}
showingEditor.set(!showing)
}}
onPointerDown={stopEventPropagation}
title={showing ? 'Hide editor' : 'Show editor'}
>
<Icon icon={showing ? 'following' : 'follow'} />
</button>
)
})

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 }
)
}
30 changes: 30 additions & 0 deletions app/components/ShowResult.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<iframe
srcDoc={html}
width={toDomPrecision(shape.props.w)}
height={toDomPrecision(shape.props.h)}
draggable={false}
style={{
flexShrink: 1,
pointerEvents: isEditing ? 'auto' : 'none',
boxShadow,
border: '1px solid var(--color-panel-contrast)',
borderRadius: 'var(--radius-2)',
}}
/>
)
}
3 changes: 0 additions & 3 deletions app/components/UrlLinkButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ export function UrlLinkButton({ uploadUrl }: { uploadUrl: string }) {
<button
style={{
all: 'unset',
position: 'absolute',
top: 40,
right: -40,
height: 40,
width: 40,
display: 'flex',
Expand Down
1 change: 0 additions & 1 deletion app/lib/getHtmlFromOpenAI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ export async function getHtmlFromOpenAI({
},
body: JSON.stringify(body),
})
console.log(resp)
json = await resp.json()
} catch (e) {
console.log(e)
Expand Down
Loading