studio: version text in the status bar, favicon
Deploy web Studio / deploy (push) Successful in 3m15s
Deploy web Studio / deploy (push) Successful in 3m15s
The status bar ends with "isokit <engine version>"; the web build adds the commit (linked on Gitea), its date as a tooltip and a "+" when built from a tree with uncommitted changes (build_web.py writes a <meta name="isokit-build"> from GITHUB_SHA or git). /api/meta now carries "engine". studio/favicon.svg (isometric cube in the accent green) is served by `isokit studio` (also on /view) and copied into the web build. check_web.py and check_studio.py check both.
This commit is contained in:
1 parent
c1d01331ac
commit
c647f39059
11 files changed
+66
-4
No files matched your search
@@ -13,7 +13,7 @@ use tiny_http::{Header, Method, Request, Response, Server};
|
||||
const VIEW_PAGE: &str = r#"<!doctype html><html lang="en"><head><meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"><title>isokit preview</title>
|
||||
<script>(function(){var t=null;try{t=localStorage.getItem("isokit-theme")}catch(e){}if(t!=="light"&&t!=="dark")t=matchMedia("(prefers-color-scheme: light)").matches?"light":"dark";document.documentElement.setAttribute("data-theme",t);})();</script>
|
||||
<link rel="stylesheet" href="/assets/viewer/isokit-view.css"><link rel="stylesheet" href="/assets/theme.css">
|
||||
<link rel="icon" type="image/svg+xml" href="/assets/studio/favicon.svg"><link rel="stylesheet" href="/assets/viewer/isokit-view.css"><link rel="stylesheet" href="/assets/theme.css">
|
||||
<style>:root{color-scheme:dark}html[data-theme=light]{color-scheme:light}body{margin:0;background:#0a0f1a;font:14px system-ui,sans-serif;color:#d6deeb}
|
||||
html[data-theme=light] body{background:#f4f6fa;color:#1b2533}main{max-width:1320px;margin:0 auto;padding:16px}</style></head>
|
||||
<body><main><div id="view"></div></main><script src="/assets/viewer/isokit-view.js"></script><script>
|
||||
@@ -164,6 +164,8 @@ pub fn studio(dir: &Path, port: u16, open: bool) -> Result<()> {
|
||||
"text/javascript; charset=utf-8"
|
||||
} else if name.ends_with(".css") {
|
||||
"text/css; charset=utf-8"
|
||||
} else if name.ends_with(".svg") {
|
||||
"image/svg+xml"
|
||||
} else {
|
||||
"text/html; charset=utf-8"
|
||||
};
|
||||
|
||||
@@ -18,6 +18,7 @@ pub fn meta<S: Store>(ws: &Workspace<S>) -> Value {
|
||||
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["engine"] = json!(env!("CARGO_PKG_VERSION"));
|
||||
v
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ pub const PAGE: &str = include_str!("../../../viewer/page.html");
|
||||
pub const STUDIO_HTML: &str = include_str!("../../../studio/index.html");
|
||||
pub const STUDIO_JS: &str = include_str!("../../../studio/studio.js");
|
||||
pub const STUDIO_CSS: &str = include_str!("../../../studio/studio.css");
|
||||
pub const STUDIO_FAVICON: &str = include_str!("../../../studio/favicon.svg");
|
||||
|
||||
/// Asset by its path in the repository ("viewer/isokit-view.js",
|
||||
/// "studio/studio.js"), from disk when `ISOKIT_ASSETS` is set.
|
||||
@@ -26,6 +27,7 @@ pub fn get(path: &str) -> Option<Cow<'static, str>> {
|
||||
"studio/index.html" => STUDIO_HTML,
|
||||
"studio/studio.js" => STUDIO_JS,
|
||||
"studio/studio.css" => STUDIO_CSS,
|
||||
"studio/favicon.svg" => STUDIO_FAVICON,
|
||||
_ => return None,
|
||||
};
|
||||
Some(Cow::Borrowed(s))
|
||||
|
||||
@@ -216,6 +216,7 @@ mod tests {
|
||||
assert_eq!(m["title"], "isokit demo works");
|
||||
assert!(m["catalog"].as_array().unwrap().len() > 20);
|
||||
assert!(m["script_error"].is_null());
|
||||
assert_eq!(m["engine"], env!("CARGO_PKG_VERSION"));
|
||||
let n = j(&s.state())["doc"]["objects"].as_array().unwrap().len();
|
||||
let v = j(&s.render("site", 1, "", true));
|
||||
assert!(
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<path d="M16 2.5 29 10 16 17.5 3 10Z" fill="#3ddc84"/>
|
||||
<path d="M3 10 16 17.5V30.5L3 23Z" fill="#1f9e5a"/>
|
||||
<path d="M29 10 16 17.5V30.5L29 23Z" fill="#126b3c"/>
|
||||
<path d="M16 2.5 29 10V23L16 30.5 3 23V10ZM3 10 16 17.5 29 10M16 17.5V30.5" fill="none" stroke="#0a0f1a" stroke-width="1.2" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 387 B |
@@ -9,6 +9,7 @@
|
||||
if(t!=="light"&&t!=="dark")t=window.matchMedia&&matchMedia("(prefers-color-scheme: light)").matches?"light":"dark";
|
||||
document.documentElement.setAttribute("data-theme",t);})();
|
||||
</script>
|
||||
<link rel="icon" type="image/svg+xml" href="/assets/studio/favicon.svg">
|
||||
<link rel="stylesheet" href="/assets/viewer/isokit-view.css">
|
||||
<link rel="stylesheet" href="/assets/theme.css">
|
||||
<link rel="stylesheet" href="/assets/studio/studio.css">
|
||||
@@ -51,6 +52,7 @@ document.documentElement.setAttribute("data-theme",t);})();
|
||||
<span class="st-grow"></span>
|
||||
<button type="button" id="warnbtn" class="st-link" hidden></button>
|
||||
<span id="stats"></span>
|
||||
<a id="ver" class="st-ver" href="https://git.b4l.co.th/B4L/isokit" target="_blank" rel="noopener"></a>
|
||||
</footer>
|
||||
</section>
|
||||
<aside class="st-right" id="right"></aside>
|
||||
|
||||
@@ -52,6 +52,8 @@ input[type=checkbox] { accent-color: var(--accent); }
|
||||
.st-hint { position: absolute; top: 52px; left: 50%; transform: translateX(-50%); background: var(--panel); border: 1px solid var(--accent); border-radius: 16px; padding: 4px 14px; z-index: 5; pointer-events: none; }
|
||||
.st-status { display: flex; align-items: center; gap: 14px; padding: 4px 10px; background: var(--panel); border-top: 1px solid var(--stroke); color: var(--mute); font-variant-numeric: tabular-nums; flex: 0 0 auto; }
|
||||
.st-status select { padding: 1px 4px; }
|
||||
.st-ver { color: var(--mute); font-size: 11px; text-decoration: none; white-space: nowrap; }
|
||||
.st-ver:hover { color: var(--fg); text-decoration: underline; }
|
||||
.st-link { background: none; border: 0; color: var(--danger); padding: 0; text-decoration: underline; }
|
||||
|
||||
.st-right h3 { margin: 0; font-size: 15px; display: flex; align-items: center; gap: 6px; }
|
||||
|
||||
@@ -356,12 +356,28 @@
|
||||
if (B && B.preview) $("preview").addEventListener("click", function (e) { e.preventDefault(); B.preview(); });
|
||||
if (B && B.saveTitle) $("save").title = B.saveTitle;
|
||||
|
||||
// engine version from /api/meta; a web build adds its commit and date
|
||||
// (<meta name="isokit-build" content="<sha> <date> [dirty]">, tools/build_web.py)
|
||||
function showVersion() {
|
||||
var el = $("ver"), m = document.querySelector('meta[name="isokit-build"]');
|
||||
var b = m ? m.getAttribute("content").split(" ") : [];
|
||||
var text = "isokit " + (S.meta.engine || "?"), tip = "isokit engine " + (S.meta.engine || "?");
|
||||
if (b[0]) {
|
||||
text += " · " + b[0].slice(0, 7) + (b[2] ? "+" : "");
|
||||
tip += ", build " + b[0] + (b[1] ? " (" + b[1] + ")" : "") + (b[2] ? ", with uncommitted changes" : "");
|
||||
el.href = "https://git.b4l.co.th/B4L/isokit/commit/" + b[0];
|
||||
}
|
||||
el.textContent = text;
|
||||
el.title = tip;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------ start
|
||||
Promise.all([api("/api/meta"), api("/api/doc")]).then(function (rs) {
|
||||
S.meta = rs[0]; takeState(rs[1]);
|
||||
document.title = S.meta.title + " · isokit Studio";
|
||||
$("title").textContent = S.meta.title;
|
||||
if (S.meta.load_error) toast(S.meta.load_error, true);
|
||||
showVersion();
|
||||
renderPalette(); renderLists(); mountView(); renderRight();
|
||||
poll();
|
||||
});
|
||||
|
||||
+24
-3
@@ -81,6 +81,24 @@ def demo_files():
|
||||
return out
|
||||
|
||||
|
||||
def build_info():
|
||||
"""'<commit> <commit date> [dirty]' for the version text in the Studio's
|
||||
status bar; GITHUB_SHA (CI) wins over git. Empty when git is missing."""
|
||||
def git(*args):
|
||||
r = subprocess.run(["git", *args], capture_output=True, text=True, cwd=ROOT)
|
||||
return r.stdout.strip() if r.returncode == 0 else ""
|
||||
try:
|
||||
sha = os.environ.get("GITHUB_SHA") or git("rev-parse", "HEAD")
|
||||
if not sha:
|
||||
return ""
|
||||
parts = [sha, git("log", "-1", "--format=%cs", sha) or "-"]
|
||||
if git("status", "--porcelain", "--untracked-files=no"):
|
||||
parts.append("dirty")
|
||||
return " ".join(parts)
|
||||
except OSError:
|
||||
return ""
|
||||
|
||||
|
||||
def replace_once(s, old, new):
|
||||
n = s.count(old)
|
||||
assert n == 1, f"studio/index.html: expected {old!r} once, found {n}"
|
||||
@@ -116,7 +134,7 @@ def main():
|
||||
print(" (wasm-opt not found: the .wasm stays unoptimised by binaryen, about 15% larger)")
|
||||
|
||||
for src, dst in [("viewer/isokit-view.js", "viewer"), ("viewer/isokit-view.css", "viewer"),
|
||||
("studio/studio.js", "studio"), ("studio/studio.css", "studio"),
|
||||
("studio/studio.js", "studio"), ("studio/studio.css", "studio"), ("studio/favicon.svg", "studio"),
|
||||
("web/web.js", "web"), ("web/web.css", "web")]:
|
||||
(out / "assets" / dst).mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(ROOT / src, out / "assets" / dst / Path(src).name)
|
||||
@@ -129,8 +147,11 @@ def main():
|
||||
v = "v=" + h.hexdigest()[:10]
|
||||
|
||||
html = (ROOT / "studio" / "index.html").read_text(encoding="utf-8")
|
||||
build = build_info()
|
||||
html = replace_once(html, '<title>isokit Studio</title>',
|
||||
'<title>isokit Studio</title>\n<meta name="description" content="isokit Studio: edit isometric site plans in the browser.">')
|
||||
'<title>isokit Studio</title>\n<meta name="description" content="isokit Studio: edit isometric site plans in the browser.">'
|
||||
+ (f'\n<meta name="isokit-build" content="{build}">' if build else ""))
|
||||
html = replace_once(html, 'href="/assets/studio/favicon.svg"', f'href="assets/studio/favicon.svg?{v}"')
|
||||
html = replace_once(html, 'href="/assets/viewer/isokit-view.css"', f'href="assets/viewer/isokit-view.css?{v}"')
|
||||
html = replace_once(html, '<link rel="stylesheet" href="/assets/theme.css">', '<style id="iso-theme"></style>')
|
||||
html = replace_once(html, '<link rel="stylesheet" href="/assets/studio/studio.css">',
|
||||
@@ -148,7 +169,7 @@ def main():
|
||||
if p.is_file():
|
||||
total += p.stat().st_size
|
||||
print(f" {p.relative_to(out).as_posix():40} {p.stat().st_size / 1024:8.1f} kB")
|
||||
print(f"{out} ({total / 1024 / 1024:.1f} MB, {v})")
|
||||
print(f"{out} ({total / 1024 / 1024:.1f} MB, {v}, build {build or 'unknown'})")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -48,6 +48,10 @@ with sync_playwright() as p:
|
||||
n0 = len(doc()["objects"])
|
||||
check(pg.locator("#objects li").count() == n0, f"object list shows {n0} objects")
|
||||
check(pg.locator(".st-tile").count() >= 20, "palette lists the prefabs")
|
||||
ver = pg.locator("#ver").inner_text()
|
||||
check(ver == "isokit " + get("/api/meta")["engine"], "status bar shows the engine version: " + ver)
|
||||
icon = urllib.request.urlopen(BASE + "/assets/studio/favicon.svg")
|
||||
check(icon.headers.get_content_type() == "image/svg+xml" and b"<svg" in icon.read(), "favicon served as SVG")
|
||||
pg.screenshot(path=str(OUT / "studio-start.jpg"), type="jpeg", quality=80)
|
||||
|
||||
# place a tree: pick it in the palette, click an empty spot of the map
|
||||
|
||||
@@ -89,6 +89,11 @@ try:
|
||||
check(pg.evaluate("() => getComputedStyle(document.querySelector('.iso-svg .iso-t')).fill") not in ("", "rgb(0, 0, 0)"),
|
||||
"theme CSS styles the scene")
|
||||
check(pg.evaluate("() => isokit.webmcp") == 15, "15 MCP tools registered with WebMCP")
|
||||
ver = pg.locator("#ver").inner_text()
|
||||
check(ver.startswith("isokit " + pg.evaluate("() => isokit.engine") + " · "), "status bar shows version and build: " + ver)
|
||||
icon = pg.evaluate("""async () => { const l = document.querySelector('link[rel=icon]');
|
||||
const r = await fetch(l.href); return r.ok && (await r.text()).includes('<svg'); }""")
|
||||
check(icon, "favicon link loads an SVG")
|
||||
shot(pg, "web-start.jpg")
|
||||
|
||||
# ---------------------------------------------------------------- edit: place, drag, props, undo
|
||||
|
||||
Reference in new issue
Block a user