aboutsummaryrefslogtreecommitdiffstats
path: root/static
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-11-09 20:41:29 +0100
committerDanilo M. <danix@danix.xyz>2026-11-09 20:41:29 +0100
commit6465ffceeef5c3df404972bf4eaa1b24e451a7c4 (patch)
tree4b1c769fb39809a9b2ca7570190baed3ecc1bde0 /static
parent853f1ed1928aaeca9ea956c24bee12cd56a7cfdd (diff)
downloaddeck-it-6465ffceeef5c3df404972bf4eaa1b24e451a7c4.tar.gz
deck-it-6465ffceeef5c3df404972bf4eaa1b24e451a7c4.zip
feat: window rows get app icons and a close/move menu
Diffstat (limited to 'static')
-rw-r--r--static/deck.css23
-rw-r--r--static/modules/windows.js102
2 files changed, 105 insertions, 20 deletions
diff --git a/static/deck.css b/static/deck.css
index c56a7e5..9aaeaec 100644
--- a/static/deck.css
+++ b/static/deck.css
@@ -263,9 +263,30 @@ select {
}
.wrow.on { color: var(--udt-accent); }
.wt { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; cursor: pointer; }
-.wclose { flex: none; width: 36px; height: 36px; padding: 0; }
+.wicon { width: 24px; height: 24px; object-fit: contain; flex: none; }
+.wmenu { flex: none; width: 40px; height: 40px; padding: 0; font-size: 18px; line-height: 1; }
.windows .launches { display: flex; flex-wrap: wrap; gap: 8px; }
+.wpop {
+ position: fixed;
+ z-index: 50;
+ min-width: 220px;
+ max-width: 80vw;
+ padding: 10px;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ background: var(--udt-mantle);
+ border: 1px solid var(--udt-surface1);
+ border-radius: 12px;
+ box-shadow: 0 12px 30px rgba(0, 0, 0, .35);
+}
+.wpop-title { font-weight: 600; font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+.wpop-label { font-size: 11px; text-transform: uppercase; letter-spacing: .08em; color: var(--udt-overlay0); }
+.wpop-close { text-align: left; }
+.wpop-ws { display: flex; flex-wrap: wrap; gap: 6px; }
+.wpop-ws button { min-width: 44px; min-height: 44px; padding: 0; font-size: 16px; font-weight: 600; }
+
.llm { display: flex; flex-direction: column; gap: 10px; }
.llm-sec {
padding: 10px 12px;
diff --git a/static/modules/windows.js b/static/modules/windows.js
index 002bb5b..c981846 100644
--- a/static/modules/windows.js
+++ b/static/modules/windows.js
@@ -18,6 +18,68 @@ function tile(label, onclick) {
return b;
}
+let pop = null;
+function closePop() {
+ if (pop) {
+ pop.remove();
+ pop = null;
+ }
+}
+
+function openMenu(anchor, win, workspaces) {
+ closePop();
+ pop = document.createElement("div");
+ pop.className = "wpop";
+ pop.innerHTML = `
+ <div class="wpop-title"></div>
+ <button class="wpop-close">Close window</button>
+ <div class="wpop-label">Move to desktop</div>
+ <div class="wpop-ws"></div>`;
+ pop.querySelector(".wpop-title").textContent = win.title || win.class;
+ pop.querySelector(".wpop-close").onclick = () => {
+ closePop();
+ post("close", { address: win.address });
+ };
+ const targets = pop.querySelector(".wpop-ws");
+ for (const n of workspaces ?? []) {
+ const b = document.createElement("button");
+ b.textContent = String(n);
+ b.onclick = () => {
+ closePop();
+ post("move", { address: win.address, n });
+ };
+ targets.append(b);
+ }
+ document.body.append(pop);
+ const r = anchor.getBoundingClientRect();
+ const width = pop.offsetWidth;
+ const height = pop.offsetHeight;
+ pop.style.top = `${Math.min(r.bottom + 6, window.innerHeight - height - 8)}px`;
+ pop.style.left = `${Math.max(8, Math.min(r.left, window.innerWidth - width - 8))}px`;
+}
+
+document.addEventListener("pointerdown", (e) => {
+ if (pop && !pop.contains(e.target) && !e.target.closest(".wmenu")) closePop();
+});
+
+function appIcon(w) {
+ if (!w.icons?.length) return null;
+ const img = document.createElement("img");
+ img.className = "wicon";
+ img.alt = "";
+ let i = 0;
+ const next = () => {
+ if (i >= w.icons.length) {
+ img.remove();
+ return;
+ }
+ img.src = `/icons/${w.icons[i++]}.svg`;
+ };
+ img.addEventListener("error", next);
+ next();
+ return img;
+}
+
export default {
title: "Windows",
tab: "Windows",
@@ -40,25 +102,6 @@ export default {
ws.append(b);
}
- const list = el.querySelector(".winlist");
- list.textContent = "";
- for (const w of (st.windows ?? []).slice(0, 8)) {
- const row = document.createElement("div");
- row.className = "wrow";
- row.classList.toggle("on", Boolean(w.active));
- const title = document.createElement("span");
- title.className = "wt";
- title.textContent = w.title || w.class;
- title.onclick = () => post("focus", { address: w.address });
- const close = document.createElement("button");
- close.className = "wclose";
- close.textContent = "×";
- close.setAttribute("aria-label", "Close window");
- close.onclick = () => post("close", { address: w.address });
- row.append(title, close);
- list.append(row);
- }
-
const launches = el.querySelector(".launches");
launches.textContent = "";
for (const item of st.launches ?? []) {
@@ -78,5 +121,26 @@ export default {
b.onclick = () => post("launch", { key: item.key });
launches.append(b);
}
+
+ const list = el.querySelector(".winlist");
+ list.textContent = "";
+ for (const w of (st.windows ?? []).slice(0, 12)) {
+ const row = document.createElement("div");
+ row.className = "wrow";
+ row.classList.toggle("on", Boolean(w.active));
+ const icon = appIcon(w);
+ if (icon) row.append(icon);
+ const title = document.createElement("span");
+ title.className = "wt";
+ title.textContent = w.title || w.class;
+ title.onclick = () => post("focus", { address: w.address });
+ const menu = document.createElement("button");
+ menu.className = "wmenu";
+ menu.textContent = "⋮";
+ menu.setAttribute("aria-label", "Window actions");
+ menu.onclick = () => openMenu(menu, w, st.workspaces);
+ row.append(title, menu);
+ list.append(row);
+ }
},
};