noclipped into a Copilot canvas
'; + return card; + } + button(label, onClick, accent = false) { + const button = document.createElement("button"); + button.textContent = label; + if (accent) { + button.className = "bv-accent"; + } + button.addEventListener("click", onClick); + return button; + } + buildMain() { + const card = this.card(); + card.appendChild(this.button("Resume", () => this.callbacks.onResume())); + card.appendChild(this.button("Relocate (new seed)", () => this.callbacks.onRelocate(), true)); + card.appendChild(this.button("Settings", () => this.show("settings"))); + card.appendChild(this.button("Help", () => this.show("help"))); + this.statsEl = document.createElement("div"); + this.statsEl.className = "bv-stats"; + card.appendChild(this.statsEl); + return card; + } + toggleRow(label, key) { + const row = document.createElement("div"); + row.className = "bv-row"; +const input = document.createElement("input"); + input.type = "checkbox"; + input.dataset.key = key; + input.setAttribute("aria-label", label); + input.addEventListener("change", () => this.callbacks.onSettingChange(key, input.checked)); + const text = document.createElement("label"); + text.textContent = label; + row.append(text, input); + return row; + } + sliderRow(label, key, min, max, stepSize) { + const row = document.createElement("div"); + row.className = "bv-row"; + const text = document.createElement("label"); + text.textContent = label; + const value = document.createElement("span"); + value.className = "bv-val"; +const input = document.createElement("input"); + input.type = "range"; + input.min = String(min); + input.max = String(max); + input.step = String(stepSize); + input.dataset.key = key; + input.setAttribute("aria-label", label); + input.addEventListener("input", () => { + value.textContent = input.value; + this.callbacks.onSettingChange(key, Number(input.value)); + }); + row.append(text, input, value); + return row; + } + heading(text) { + const h = document.createElement("div"); + h.className = "bv-h"; + h.textContent = text; + return h; + } + selectRow(label, key, options) { + const row = document.createElement("div"); + row.className = "bv-row"; + const text = document.createElement("label"); + text.textContent = label; +const select = document.createElement("select"); + select.dataset.key = key; + select.setAttribute("aria-label", label); + for (const option of options) { + const el = document.createElement("option"); + el.value = option.value; + el.textContent = option.label; + select.appendChild(el); + } + select.addEventListener("change", () => this.callbacks.onSettingChange(key, select.value)); + row.append(text, select); + return row; + } + buildSettings() { + const card = this.card(); + card.appendChild(this.heading("Camera")); + card.appendChild(this.toggleRow("Camera shake", "cameraShake")); + card.appendChild(this.toggleRow("Film grain", "filmGrain")); + card.appendChild(this.toggleRow("Camcorder HUD", "vhsHud")); + card.appendChild(this.heading("Controls")); + card.appendChild(this.toggleRow("Mouse look", "mouseLook")); + card.appendChild(this.toggleRow("Invert turn", "invertTurn")); + card.appendChild(this.toggleRow("Invert strafe", "invertStrafe")); + card.appendChild(this.toggleRow("Invert forward/back", "invertForward")); + card.appendChild(this.sliderRow("Walk speed", "moveSpeed", 0.5, 6, 0.1)); + card.appendChild(this.heading("Materials")); + card.appendChild( + this.selectRow("Wall material", "materialPreset", [ + { value: "classic", label: "Classic wallpaper mix" }, + { value: "office", label: "Plain drywall" }, + { value: "pool", label: "Ceramic tile" }, + { value: "concrete", label: "Bare concrete" }, + { value: "panel", label: "Wood paneling" } + ]) + ); + card.appendChild(this.sliderRow("Hue shift", "materialHueShift", -180, 180, 5)); + card.appendChild(this.sliderRow("Brightness", "materialBrightness", 0.6, 1.4, 0.05)); + card.appendChild(this.heading("Monster")); + card.appendChild(this.toggleRow("Monster", "monsterEnabled")); + card.appendChild( + this.selectRow("Form", "monsterForm", [ + { value: "spider", label: "Spider-like" }, + { value: "humanoid", label: "Human-like" }, + { value: "cloud", label: "Cloud-like" }, + { value: "random", label: "Random each spawn" } + ]) + ); + card.appendChild(this.sliderRow("Speed", "monsterSpeed", 0.5, 5, 0.1)); + card.appendChild(this.sliderRow("Spawn after (min)", "monsterSpawnMin", 0.1, 10, 0.1)); + card.appendChild(this.sliderRow("Spawn before (min)", "monsterSpawnMax", 0.5, 15, 0.5)); + card.appendChild(this.heading("World")); + card.appendChild(this.toggleRow("Furniture", "furniture")); + card.appendChild(this.toggleRow("Wallpaper shifts", "wallpaperShifts")); + card.appendChild(this.sliderRow("Render distance", "renderDistance", 6, 28, 1)); + card.appendChild(this.heading("Copilot")); + card.appendChild(this.toggleRow("Ghost-writer on the walls", "copilotGhostWriter")); + const seedRow = document.createElement("div"); + seedRow.className = "bv-row"; + const seedLabel = document.createElement("label"); + seedLabel.textContent = "Seed (0 = random)"; +const seedInput = document.createElement("input"); + seedInput.type = "number"; + seedInput.dataset.key = "seed"; + seedInput.setAttribute("aria-label", "Seed (0 = random)"); + seedInput.addEventListener("change", () => { + const seed = Math.trunc(Number(seedInput.value)) || 0; + this.callbacks.onSettingChange("seed", seed); + this.callbacks.onReseed(seed); + }); + seedRow.append(seedLabel, seedInput); + card.appendChild(seedRow); + const back = this.button("Back", () => this.show("main")); + back.classList.add("bv-back"); + card.appendChild(back); + return card; + } + buildHelp() { + const card = this.card(); + const help = document.createElement("div"); + help.className = "bv-help"; + help.innerHTML = "W/S or ↑/↓ walk${esc(linkedNamespace)} is already linked.`
+ : "Choose which connector namespace to browse. This choice is saved for future sessions.";
+ const defaultSigninMessage = hasLinkedNamespace
+ ? "Sign in to Azure to view and manage its connectors."
+ : "Sign in to Azure to load your subscriptions and connector namespaces.";
const subOptions = subscriptions.map((s) =>
``
).join("");
return `
-saved-gateway<\/code> is already linked\./);
+ assert.match(html, /Sign in to Azure to view and manage its connectors\./);
+ assert.match(html, /const hasLinkedNamespace = true/);
+ assert.match(html, /window\.location\.replace\("\/"\)/);
+ assert.match(html, //);
+});
+
+test("linked namespace sign-in escapes saved names and produces valid client JavaScript", () => {
+ const html = renderSetupHtml([], "", "token", {
+ linkedNamespace: `gateway`,
+ });
+ assert.doesNotMatch(html, /