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 indication in the browser and cli that circuit is currently loading #51

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
57 changes: 43 additions & 14 deletions cli/dev/DevServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,26 +121,55 @@ circuit.add(<MyCircuit />)

async handleFileChangedOnFilesystem(absoluteFilePath: string) {
const relativeFilePath = path.relative(this.projectDir, absoluteFilePath)
// We've temporarily disabled upserting manual edits from filesystem changes
// because it can be edited by the browser
if (relativeFilePath.includes("manual-edits.json")) return
console.log(`\nFile change detected: ${relativeFilePath}`)

if (relativeFilePath.includes("manual-edits.json")) {
console.log("Skipping manual-edits.json update")
return
}

await this.typesHandler?.handleFileTypeDependencies(absoluteFilePath)

console.log(`${relativeFilePath} saved. Applying changes...`)
await this.fsKy
.post("api/files/upsert", {
json: {
file_path: relativeFilePath,
text_content: fs.readFileSync(absoluteFilePath, "utf-8"),
initiator: "filesystem_change",
},
})
.json()
console.log("Sending file update event to clients...")
try {
if (this.httpServer) {
const sseClients = (this.httpServer as any).sseClients
if (sseClients?.size > 0) {
console.log(`Broadcasting to ${sseClients.size} clients`)
const event = {
type: "FILE_UPDATED",
file: relativeFilePath,
timestamp: Date.now(),
}

for (const client of sseClients) {
client.write(`data: ${JSON.stringify(event)}\n\n`)
}
console.log("Event broadcast complete")
} else {
console.log("No SSE clients connected")
}
} else {
console.log("HTTP server not initialized")
}

console.log("Updating file on server...")
await this.fsKy
.post("api/files/upsert", {
json: {
file_path: relativeFilePath,
text_content: fs.readFileSync(absoluteFilePath, "utf-8"),
initiator: "filesystem_change",
},
})
.json()
console.log("File update complete")
} catch (error) {
console.error("Error in handleFileChangedOnFilesystem:", error)
}
}

async upsertInitialFiles() {
// Scan project directory for all files and upsert them
const fileNames = fs.readdirSync(this.projectDir)
for (const fileName of fileNames) {
if (fs.statSync(path.join(this.projectDir, fileName)).isDirectory())
Expand Down
3 changes: 3 additions & 0 deletions cli/dev/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export const registerDev = (program: Command) => {
.argument("[file]", "Path to the snippet file")
.option("-p, --port <number>", "Port to run server on", "3020")
.action(async (file: string, options: { port: string }) => {
console.log("Starting the development server, please wait...")

let port = parseInt(options.port)

const isPortAvailable = (port: number): Promise<boolean> => {
Expand Down Expand Up @@ -72,5 +74,6 @@ export const registerDev = (program: Command) => {

await server.start()
await server.addEntrypoint()
console.log("Development server started successfully.")
})
}
24 changes: 24 additions & 0 deletions lib/server/createHttpServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,33 @@ import { getIndex } from "../site/getIndex"

export const createHttpServer = async (port = 3020) => {
const fileServerHandler = getNodeHandler(winterspecBundle as any, {})
let sseClients = new Set<http.ServerResponse>()

const server = http.createServer(async (req, res) => {
const url = new URL(req.url!, `http://${req.headers.host}`)

if (url.pathname === "/api/events/stream") {
console.log("Client connecting to SSE stream")
res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
})

sseClients.add(res)
console.log(`SSE client connected. Total clients: ${sseClients.size}`)

res.write('data: {"type":"connected"}\n\n')

req.on("close", () => {
console.log("SSE client disconnected")
sseClients.delete(res)
console.log(`Remaining clients: ${sseClients.size}`)
})

return
}

if (url.pathname === "/standalone.min.js") {
const standaloneFilePath =
process.env.RUNFRAME_STANDALONE_FILE_PATH ||
Expand Down Expand Up @@ -59,6 +82,7 @@ export const createHttpServer = async (port = 3020) => {
})

return new Promise<{ server: http.Server }>((resolve) => {
;(server as any).sseClients = sseClients // Make clients accessible
server.listen(port, () => {
console.log(`Server running at http://localhost:${port}`)
resolve({ server })
Expand Down
87 changes: 86 additions & 1 deletion lib/site/getIndex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,97 @@ import pkg from "../../package.json"
export const getIndex = async () => {
return `<html>
<head>
<script src="https://cdn.tailwindcss.com"></script>
<style>
.circuit-loading {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.9);
display: none;
justify-content: center;
align-items: center;
z-index: 1000;
}
.circuit-loading.active {
display: flex;
}
.loading-content {
background: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
text-align: center;
}
.loading-spinner {
border: 3px solid #e5e7eb;
border-top: 3px solid #3b82f6;
border-radius: 50%;
width: 2rem;
height: 2rem;
animation: spin 1s linear infinite;
margin: 0 auto 1rem;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body>
<script src="https://cdn.tailwindcss.com"></script>
<div id="root">loading...</div>
<div class="circuit-loading">
<div class="loading-content">
<div class="loading-spinner"></div>
<p>Circuit is rendering...</p>
</div>
</div>
<script>
globalThis.process = { env: { NODE_ENV: "production" } }
globalThis.process = { env: { NODE_ENV: "production" } }
console.log('Setting up EventSource connection...');

const loadingOverlay = document.querySelector('.circuit-loading');
const evtSource = new EventSource('/api/events/stream');

evtSource.onopen = () => {
console.log('EventSource connection established');
};

evtSource.onerror = (error) => {
console.error('EventSource error:', error);
};

evtSource.onmessage = (event) => {
console.log('Received event:', event.data);
const data = JSON.parse(event.data);

if (data.type === 'FILE_UPDATED') {
console.log('File update detected, showing loading overlay');
loadingOverlay.classList.add('active');

console.log('Replacing standalone.js script');
const oldScript = document.querySelector('script[src="/standalone.min.js"]');
const newScript = document.createElement('script');
newScript.src = '/standalone.min.js';

newScript.onload = () => {
console.log('New script loaded, hiding overlay after delay');
setTimeout(() => {
loadingOverlay.classList.remove('active');
console.log('Loading overlay hidden');
}, 500);
};

newScript.onerror = (error) => {
console.error('Error loading new script:', error);
};

oldScript.parentNode.replaceChild(newScript, oldScript);
}
};
</script>
<script src="/standalone.min.js"></script>
</body>
Expand Down