BareGit

Add plan serialization

Author: MetroWind <chris.corsair@gmail.com>
Date: Sat Aug 30 16:24:35 2025 -0700
Commit: 6a6cc2f22e806be895f9fddc9ed6cabab395b6cb

Changes

diff --git a/scripts/lib.js b/scripts/lib.js
index 4e4850e..9a5e957 100644
--- a/scripts/lib.js
+++ b/scripts/lib.js
@@ -2,9 +2,39 @@ const h = preact.h;
 const CELL_SIZE = 32;
 const GRID_SIZE_X = 21;
 const GRID_SIZE_Y = 21;
+const FLOOR_COUNT = 15;
 
 const ASSET_SVG_PROPS = {"width": CELL_SIZE, "height": CELL_SIZE,
                          "version": "1.1"};
 const ASSET_WHITE_BG = h("rect", {"x": 0, "y": 0, "width": CELL_SIZE,
                                   "height": CELL_SIZE, "fill": "white",
                                   "stroke-width": 0 });
+
+// Bytes is a Uint8Array
+async function compressBytes(bytes)
+{
+    let blob = new Blob([bytes]);
+    const ds = new CompressionStream("deflate");
+    const compressed = blob.stream().pipeThrough(ds);
+    let compressed_blob = await new Response(compressed).blob();
+    const reader = new FileReader();
+    return new Promise((resolve, _) => {
+        reader.onloadend = (event) => {
+            const result = event.target.result;
+            resolve(result.replace(/^data:.+;base64,/, '')
+                    .replaceAll("/", "-").replaceAll("+", "_"));
+        }
+        reader.readAsDataURL(compressed_blob);
+    });
+}
+
+// Decompress into Uint8Array
+function decompressBytes(s)
+{
+    const decoded = window.atob(s.replaceAll("_", "+").replaceAll("-", "/"));
+    const decoded_array = Uint8Array.from(decoded, c => c.charCodeAt(0));
+    let blob = new Blob([decoded_array]);
+    const cs = new DecompressionStream("deflate");
+    const decompressed = blob.stream().pipeThrough(cs);
+    return new Response(decompressed).bytes();
+}
diff --git a/scripts/main.js b/scripts/main.js
index 01301a1..7347f37 100644
--- a/scripts/main.js
+++ b/scripts/main.js
@@ -3,8 +3,15 @@ const DEFAULT_APP_STATE = {
     plan: new Array(GRID_SIZE_X * GRID_SIZE_Y).fill(0),
     // mouse_state can be “normal”, or “dnd”.
     mouse_state: "normal",
+    plan_code: "",
 };
 
+async function serializePlan(app_state)
+{
+    let rest = new Array(GRID_SIZE_X * GRID_SIZE_Y * (FLOOR_COUNT - 1)).fill(0);
+    return compressBytes(Uint8Array.from(app_state.plan.concat(rest)));
+}
+
 function genGrid(x_count, y_count, cell_size)
 {
     let lines = [];
@@ -62,7 +69,6 @@ function PlanGridView({content, mouse_state})
     }
 
     let hilight_box = null;
-    console.debug(mouse_coord);
 
     if(mouse_coord !== null && mouse_state != "dnd")
     {
@@ -130,7 +136,13 @@ function App({initial_state})
         let new_state = structuredClone(state);
         new_state.plan[cell_index] = asset_index;
         new_state.mouse_state = "normal";
-        setState(new_state);
+        serializePlan(new_state).then((s) => {
+            new_state.plan_code = s;
+            setState(new_state);
+            const url = new URL(location);
+            url.searchParams.set("plan", s);
+            window.history.pushState({}, "", url);
+        });
     }
 
     function onClickSaveImg()
@@ -157,13 +169,37 @@ function App({initial_state})
     }
 
     return h("div", {},
+             h("h2", {}, `Floor ${state.floor}`),
              h(AssetsView, {on_drag_begin: onDragAssetBegin,
                             on_drag_end: onDragAssetEnd}),
              h(PlanView, {plan: state.plan, mouse_state: state.mouse_state}),
              h("div", {},
                h("a", {"href": "javascript:void(0);", onclick: onClickSaveImg},
-                 "Open Image!")));
+                 "Open Image!")),
+             h("div", {},
+               h("textarea", {readonly: true}, state.plan_code)),
+            );
 }
 
-preact.render(h(App, {initial_state: DEFAULT_APP_STATE}),
-              document.getElementById("Plan"));
+function render(app_state)
+{
+    preact.render(h(App, {initial_state: app_state}),
+                  document.getElementById("Plan"));
+}
+
+const paramsString = window.location.search;
+const searchParams = new URLSearchParams(paramsString);
+const encoded_plan = searchParams.get("plan");
+if(encoded_plan === null)
+{
+    render(DEFAULT_APP_STATE);
+}
+else
+{
+    decompressBytes(encoded_plan).then(a => {
+        let state = structuredClone(DEFAULT_APP_STATE);
+        state.plan = Array.from(a.subarray(0, GRID_SIZE_X * GRID_SIZE_Y));
+        state.plan_code = encoded_plan;
+        render(state);
+    });
+}