isokit: web Studio (engine as WASM, projects in the browser or a live folder)
- isokit-wasm: wasm-bindgen build of the engine; a project in memory behind the same JSON API as `isokit studio`, plus the MCP tools - isokit-project: Store trait (FsStore folder, MemStore memory) under a generic Workspace; Project::from_files; shared Studio API module (api.rs) used by serve.rs and the WASM build; web_time for clocks in the browser - studio.js runs on a pluggable backend (window.IsoKitBackend); no Studio keys while a dialog is open - web/: projects in IndexedDB, new/demo/delete, zip export/import (stored and deflated), folder import by drop or picker, folders opened live via File System Access with outside edits followed, code editor, page build and preview, two tabs in step, isokit.mcp() and WebMCP registration - tools/build_web.py -> dist/web (static), tools/check_web.py (44 checks)
This commit is contained in:
1 parent
564aeb75f6
commit
5eb60d2a0c
20 files changed
+1950
-178
No files matched your search
@@ -3,9 +3,8 @@
|
||||
//! live. Single-threaded: one request at a time against one Workspace.
|
||||
|
||||
use anyhow::Result;
|
||||
use isokit_project::ops::Op;
|
||||
use isokit_project::workspace::Workspace;
|
||||
use isokit_project::{assets, check, mcp, page};
|
||||
use isokit_project::{api, assets, check, mcp, page};
|
||||
use serde_json::{json, Map, Value};
|
||||
use std::io::Read;
|
||||
use std::path::Path;
|
||||
@@ -100,18 +99,6 @@ fn origin_ok(req: &Request) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
fn state(ws: &Workspace) -> Value {
|
||||
json!({ "doc": ws.project.doc, "version": ws.version.to_string(), "can_undo": ws.can_undo(), "can_redo": ws.can_redo() })
|
||||
}
|
||||
|
||||
fn meta(ws: &Workspace) -> Value {
|
||||
let mut v = serde_json::to_value(ws.project.meta()).unwrap_or_default();
|
||||
v["dir"] = json!(ws.dir.display().to_string());
|
||||
v["load_error"] = json!(ws.load_error);
|
||||
v["script_error"] = json!(ws.project.runtime.as_ref().err());
|
||||
v
|
||||
}
|
||||
|
||||
pub fn studio(dir: &Path, port: u16, open: bool) -> Result<()> {
|
||||
let dir = std::fs::canonicalize(dir).unwrap_or_else(|_| dir.to_path_buf());
|
||||
// Windows canonical paths carry a \?\ prefix that other tools do not accept
|
||||
@@ -191,21 +178,16 @@ pub fn studio(dir: &Path, port: u16, open: bool) -> Result<()> {
|
||||
}
|
||||
(Method::Get, "/api/meta") => {
|
||||
ws.refresh();
|
||||
send_json(req, meta(&ws))
|
||||
send_json(req, api::meta(&ws))
|
||||
}
|
||||
(Method::Get, "/api/doc") => {
|
||||
ws.refresh();
|
||||
send_json(req, state(&ws))
|
||||
send_json(req, api::state(&ws))
|
||||
}
|
||||
(Method::Get, "/api/render") => {
|
||||
ws.refresh();
|
||||
let q = query(&url);
|
||||
let p = &ws.project;
|
||||
let level = q
|
||||
.get("level")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(str::to_string)
|
||||
.unwrap_or_else(|| p.manifest.default_level());
|
||||
let level = q.get("level").and_then(|v| v.as_str());
|
||||
let rot = q
|
||||
.get("rot")
|
||||
.and_then(|v| v.as_str())
|
||||
@@ -216,85 +198,40 @@ pub fn studio(dir: &Path, port: u16, open: bool) -> Result<()> {
|
||||
.and_then(|v| v.as_str())
|
||||
.and_then(|s| serde_json::from_str::<Map<String, Value>>(s).ok());
|
||||
let dev = q.get("dev").is_some();
|
||||
match p.render(&level, rot, cfg.as_ref(), dev) {
|
||||
Ok(v) => send_json(req, serde_json::to_value(v).unwrap_or_default()),
|
||||
Err(e) => send(
|
||||
req,
|
||||
200,
|
||||
"application/json",
|
||||
json!({ "error": e }).to_string(),
|
||||
),
|
||||
}
|
||||
// script errors are an answer for the viewer, not a failed request
|
||||
send(
|
||||
req,
|
||||
200,
|
||||
"application/json; charset=utf-8",
|
||||
api::render(&ws, level, rot, cfg.as_ref(), dev).to_string(),
|
||||
)
|
||||
}
|
||||
(Method::Get, "/api/check") => {
|
||||
ws.refresh();
|
||||
send_json(req, json!({ "findings": check::check(&ws.project) }))
|
||||
}
|
||||
(Method::Post, "/api/op") => {
|
||||
let r = if let Some(list) = bodyv.get("ops").and_then(|v| v.as_array()) {
|
||||
let ops: Result<Vec<Op>, String> = list
|
||||
.iter()
|
||||
.map(|v| serde_json::from_value::<Op>(v.clone()).map_err(|e| e.to_string()))
|
||||
.collect();
|
||||
ops.and_then(|l| ws.apply_all(l))
|
||||
.map(|o| o.last().and_then(|x| x.id.clone()))
|
||||
} else {
|
||||
serde_json::from_value::<Op>(bodyv.clone())
|
||||
.map_err(|e| format!("bad operation: {e}"))
|
||||
.and_then(|op| ws.apply(op))
|
||||
.map(|o| o.id)
|
||||
};
|
||||
match r {
|
||||
Ok(id) => {
|
||||
let mut s = state(&ws);
|
||||
s["ok"] = json!(true);
|
||||
s["id"] = json!(id);
|
||||
send_json(req, s)
|
||||
}
|
||||
Err(e) => send(
|
||||
req,
|
||||
200,
|
||||
"application/json",
|
||||
json!({ "error": e }).to_string(),
|
||||
),
|
||||
}
|
||||
let r = api::op(&mut ws, &bodyv);
|
||||
send(req, 200, "application/json; charset=utf-8", r.to_string())
|
||||
}
|
||||
(Method::Post, "/api/undo") | (Method::Post, "/api/redo") => {
|
||||
let r = if path.ends_with("undo") {
|
||||
ws.undo()
|
||||
} else {
|
||||
ws.redo()
|
||||
};
|
||||
match r {
|
||||
Ok(_) => send_json(req, state(&ws)),
|
||||
Err(e) => send(
|
||||
req,
|
||||
200,
|
||||
"application/json",
|
||||
json!({ "error": e }).to_string(),
|
||||
),
|
||||
}
|
||||
let r = api::undo(&mut ws, path.ends_with("redo"));
|
||||
send(req, 200, "application/json; charset=utf-8", r.to_string())
|
||||
}
|
||||
(Method::Post, "/api/build") => {
|
||||
ws.refresh();
|
||||
let p = &ws.project;
|
||||
let out = p.dir.join(
|
||||
p.manifest
|
||||
.build
|
||||
.out
|
||||
.clone()
|
||||
.unwrap_or_else(|| "dist/index.html".into()),
|
||||
);
|
||||
let r = page::static_page(p)
|
||||
let out = ws
|
||||
.project
|
||||
.manifest
|
||||
.build
|
||||
.out
|
||||
.clone()
|
||||
.unwrap_or_else(|| "dist/index.html".into());
|
||||
let r = page::static_page(&ws.project)
|
||||
.map_err(|e| e.to_string())
|
||||
.and_then(|html| {
|
||||
if let Some(d) = out.parent() {
|
||||
std::fs::create_dir_all(d).map_err(|e| e.to_string())?;
|
||||
}
|
||||
std::fs::write(&out, html).map_err(|e| e.to_string())
|
||||
});
|
||||
.and_then(|html| ws.write_file(&out, &html));
|
||||
match r {
|
||||
Ok(_) => send_json(req, json!({ "path": out.display().to_string() })),
|
||||
Ok(path) => send_json(req, json!({ "path": path.display().to_string() })),
|
||||
Err(e) => send(
|
||||
req,
|
||||
200,
|
||||
|
||||
Reference in new issue
Block a user