Skip to content

Commit

Permalink
Manual Edits File Editting, Minor one-way dataflow refactor (#162)
Browse files Browse the repository at this point in the history
* feat: manual edits can be passed as props

* update lockfile

* format fix

* variable name updated

* fix: Render pcb-viewer only on completion

* format update

* rename variable

* refactor hook for one way dataflow for manual edits file

* dont apply in-progress events

* fix type issue

---------

Co-authored-by: Rishabh Gupta <[email protected]>
  • Loading branch information
seveibar and imrishabh18 authored Nov 2, 2024
1 parent 76e8a39 commit 0f07d37
Show file tree
Hide file tree
Showing 13 changed files with 201 additions and 168 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,7 @@ dist-ssr
.env
.env.*
package-lock.json
test-results
test-results

.yalc
yalc.lock
Binary file modified bun.lockb
Binary file not shown.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
"@tscircuit/layout": "^0.0.29",
"@tscircuit/mm": "^0.0.8",
"@tscircuit/pcb-viewer": "^1.10.16",
"@tscircuit/props": "^0.0.77",
"@tscircuit/props": "^0.0.78",
"@tscircuit/schematic-viewer": "^1.4.2",
"@types/file-saver": "^2.0.7",
"@types/ms": "^0.7.34",
Expand Down Expand Up @@ -111,7 +111,7 @@
"@babel/standalone": "^7.25.6",
"@biomejs/biome": "^1.9.2",
"@playwright/test": "^1.48.0",
"@tscircuit/core": "^0.0.142",
"@tscircuit/core": "^0.0.144",
"@tscircuit/prompt-benchmarks": "^0.0.14",
"@types/babel__standalone": "^7.1.7",
"@types/bun": "^1.1.10",
Expand Down
1 change: 0 additions & 1 deletion src/components/AiChatInterface.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ export default function AIChatInterface({
}, [messages])

const addMessage = async (message: string) => {
console.log("adding message", message)
const newMessages = messages.concat([
{
sender: "user",
Expand Down
35 changes: 24 additions & 11 deletions src/components/CodeAndPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { decodeUrlHashToText } from "@/lib/decodeUrlHashToText"
import { getSnippetTemplate } from "@/lib/get-snippet-template"
import { cn } from "@/lib/utils"
import "@/prettier"
import manualEditsTemplate from "@/lib/templates/manual-edits-template"
import type { Snippet } from "fake-snippets-api/lib/db/schema"
import { Loader2 } from "lucide-react"
import { useEffect, useMemo, useState } from "react"
Expand All @@ -32,10 +33,15 @@ export function CodeAndPreview({ snippet }: Props) {
return (
decodeUrlHashToText(window.location.toString()) ??
snippet?.code ??
// If the snippet_id is in the url, use an empty string as the default
// code until the snippet code is loaded
(urlParams.snippet_id && "") ??
templateFromUrl.code
)
}, [])
const [manualEditsJson, setManualEditsJson] = useState("")
const [manualEditsFileContent, setManualEditsFileContent] = useState(
JSON.stringify(manualEditsTemplate, null, 2) ?? "",
)
const [code, setCode] = useState(defaultCode ?? "")
const [dts, setDts] = useState("")
const [showPreview, setShowPreview] = useState(true)
Expand All @@ -50,6 +56,13 @@ export function CodeAndPreview({ snippet }: Props) {

const { toast } = useToast()

const userImports = useMemo(
() => ({
"./manual-edits.json": JSON.parse(manualEditsFileContent),
}),
[manualEditsFileContent],
)

const {
message,
circuitJson,
Expand All @@ -58,6 +71,7 @@ export function CodeAndPreview({ snippet }: Props) {
tsxRunTriggerCount,
} = useRunTsx({
code,
userImports,
type: snippetType,
})
const qc = useQueryClient()
Expand Down Expand Up @@ -138,13 +152,12 @@ export function CodeAndPreview({ snippet }: Props) {
>
<CodeEditor
initialCode={code}
manualEditsJson={manualEditsJson}
onCodeChange={(newCode, filename) => {
if (filename === "index.tsx") {
setCode(newCode)
} else if (filename === "manual-edits.json") {
setManualEditsJson(newCode)
}
manualEditsFileContent={manualEditsFileContent}
onManualEditsFileContentChanged={(newContent) => {
setManualEditsFileContent(newContent)
}}
onCodeChange={(newCode) => {
setCode(newCode)
}}
onDtsChange={(newDts) => setDts(newDts)}
/>
Expand All @@ -157,9 +170,9 @@ export function CodeAndPreview({ snippet }: Props) {
tsxRunTriggerCount={tsxRunTriggerCount}
errorMessage={message}
circuitJson={circuitJson}
manualEditsJson={manualEditsJson}
onManualEditsJsonChange={(newManualEditsJson) => {
setManualEditsJson(newManualEditsJson)
manualEditsFileContent={manualEditsFileContent}
onManualEditsFileContentChange={(newManualEditsFileContent) => {
setManualEditsFileContent(newManualEditsFileContent)
}}
/>
)}
Expand Down
139 changes: 72 additions & 67 deletions src/components/CodeEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { useSnippetsBaseApiUrl } from "@/hooks/use-snippets-base-api-url"
import { basicSetup } from "@/lib/codemirror/basic-setup"
import manualEditsTemplate from "@/lib/templates/manual-edits-template"
import { autocompletion } from "@codemirror/autocomplete"
import { indentWithTab } from "@codemirror/commands"
import { javascript } from "@codemirror/lang-javascript"
Expand All @@ -21,10 +23,9 @@ import {
tsSync,
} from "@valtown/codemirror-ts"
import { EditorView } from "codemirror"
import { useEffect, useRef, useState } from "react"
import { useEffect, useMemo, useRef, useState } from "react"
import ts from "typescript"
import CodeEditorHeader from "./CodeEditorHeader"
import { basicSetup } from "@/lib/codemirror/basic-setup"

const defaultImports = `
import React from "@types/react/jsx-runtime"
Expand All @@ -36,72 +37,47 @@ export const CodeEditor = ({
onDtsChange,
readOnly = false,
initialCode = "",
manualEditsJson,
manualEditsFileContent,
isStreaming = false,
showImportAndFormatButtons = true,
onManualEditsFileContentChanged,
}: {
onCodeChange: (code: string, filename?: string) => void
onDtsChange?: (dts: string) => void
initialCode: string
readOnly?: boolean
isStreaming?: boolean
manualEditsJson: string
manualEditsFileContent: string
showImportAndFormatButtons?: boolean
onManualEditsFileContentChanged?: (newContent: string) => void
}) => {
const editorRef = useRef<HTMLDivElement>(null)
const viewRef = useRef<EditorView | null>(null)
const ataRef = useRef<ReturnType<typeof setupTypeAcquisition> | null>(null)
const apiUrl = useSnippetsBaseApiUrl()

const [files, setFiles] = useState<Record<string, string>>({
"index.tsx": initialCode,
"manual-edits.json": JSON.stringify(
{
pcb_placements: [],
edit_events: [],
manual_trace_hints: [],
},
null,
2,
),
})
const [currentFile, setCurrentFile] = useState("index.tsx")
const [code, setCode] = useState(initialCode)

const { ["index.tsx"]: code } = files
const files = useMemo(
() => ({
"index.tsx": code,
"manual-edits.json": manualEditsFileContent,
}),
[code, manualEditsFileContent],
)
const [currentFile, setCurrentFile] =
useState<keyof typeof files>("index.tsx")

useEffect(() => {
if (initialCode !== code) {
setFiles((prev) => ({
...prev,
"index.tsx": initialCode,
}))
}
}, [initialCode])
const isInitialCodeLoaded = Boolean(initialCode)

useEffect(() => {
if (manualEditsJson) {
setFiles((prev) => ({
...prev,
"manual-edits.json": manualEditsJson,
}))

// If currently viewing manual-edits.json, update editor content
if (currentFile === "manual-edits.json" && viewRef.current) {
viewRef.current.dispatch({
changes: {
from: 0,
to: viewRef.current.state.doc.length,
insert: manualEditsJson,
},
})
if (initialCode !== code) {
setCode(initialCode)
if (currentFile === "index.tsx") {
updateCurrentEditorContent(initialCode)
}
}
}, [manualEditsJson])

const handleImportClick = (importName: string) => {
const [owner, name] = importName.replace("@tsci/", "").split(".")
window.open(`/${owner}/${name}`, "_blank")
}
}, [isInitialCodeLoaded])

useEffect(() => {
if (!editorRef.current) return
Expand Down Expand Up @@ -190,11 +166,14 @@ export const CodeEditor = ({
EditorView.updateListener.of((update) => {
if (update.docChanged) {
const newContent = update.state.doc.toString()
setFiles((prev) => ({
...prev,
[currentFile]: newContent,
}))
onCodeChange(newContent, currentFile)
if (newContent === files[currentFile]) return

if (currentFile === "index.tsx") {
setCode(newContent)
onCodeChange(newContent)
} else if (currentFile === "manual-edits.json") {
onManualEditsFileContentChanged?.(newContent)
}

if (currentFile === "index.tsx") {
const { outputFiles } = env.languageService.getEmitOutput(
Expand Down Expand Up @@ -260,7 +239,10 @@ export const CodeEditor = ({
const start = line.indexOf(importName)
const end = start + importName.length
if (pos >= from + start && pos <= from + end) {
handleImportClick(importName)
const [owner, name] = importName
.replace("@tsci/", "")
.split(".")
window.open(`/${owner}/${name}`, "_blank")
return true
}
}
Expand Down Expand Up @@ -323,17 +305,22 @@ export const CodeEditor = ({
}
}, [!isStreaming, currentFile, code !== ""])

useEffect(() => {
const updateCurrentEditorContent = (newContent: string) => {
if (viewRef.current) {
const state = viewRef.current.state
const currentContent = files[currentFile] || ""
if (state.doc.toString() !== currentContent) {
if (state.doc.toString() !== newContent) {
viewRef.current.dispatch({
changes: { from: 0, to: state.doc.length, insert: currentContent },
changes: { from: 0, to: state.doc.length, insert: newContent },
})
}
}
}, [files[currentFile], currentFile])
}

const updateEditorToMatchCurrentFile = () => {
const currentContent = files[currentFile] || ""
updateCurrentEditorContent(currentContent)
}

const codeImports = getImportsFromCode(code)

useEffect(() => {
Expand All @@ -342,17 +329,20 @@ export const CodeEditor = ({
}
}, [codeImports])

const handleFileChange = (filename: string) => {
const handleFileChange = (filename: keyof typeof files) => {
setCurrentFile(filename)
}

const updateFileContent = (filename: string, newContent: string) => {
setFiles((prev) => ({
...prev,
[filename]: newContent,
}))

onCodeChange(newContent, filename)
const updateFileContent = (
filename: keyof typeof files,
newContent: string,
) => {
if (filename === "index.tsx") {
setCode(newContent)
onCodeChange(newContent)
} else if (filename === "manual-edits.json") {
onManualEditsFileContentChanged?.(newContent)
}

if (viewRef.current && currentFile === filename) {
viewRef.current.dispatch({
Expand All @@ -365,6 +355,19 @@ export const CodeEditor = ({
}
}

// Whenever the current file changes, updated the editor content
useEffect(() => {
updateEditorToMatchCurrentFile()
}, [currentFile])

// Whenever the manual edits json content changes, update the editor if
// it's currently viewing the manual edits file
useEffect(() => {
if (currentFile === "manual-edits.json") {
updateEditorToMatchCurrentFile()
}
}, [manualEditsFileContent])

if (isStreaming) {
return (
<div className="font-mono whitespace-pre-wrap text-xs">
Expand All @@ -380,7 +383,9 @@ export const CodeEditor = ({
currentFile={currentFile}
files={files}
handleFileChange={handleFileChange}
updateFileContent={updateFileContent}
updateFileContent={(...args) => {
return updateFileContent(...args)
}}
/>
)}
<div ref={editorRef} className="flex-1 overflow-auto" />
Expand Down
10 changes: 6 additions & 4 deletions src/components/CodeEditorHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ import {
import { useImportSnippetDialog } from "./dialogs/import-snippet-dialog"
import { useToast } from "@/hooks/use-toast"

type FileName = "index.tsx" | "manual-edits.json"

interface CodeEditorHeaderProps {
currentFile: string
files: Record<string, string>
handleFileChange: (filename: string) => void
updateFileContent: (filename: string, content: string) => void
currentFile: FileName
files: Record<FileName, string>
handleFileChange: (filename: FileName) => void
updateFileContent: (filename: FileName, content: string) => void
}

export const CodeEditorHeader = ({
Expand Down
Loading

0 comments on commit 0f07d37

Please sign in to comment.