Compare commits
No commits in common. "fix/web-ui-theme-sync" and "main" have entirely different histories.
fix/web-ui
...
main
BIN
icon.png
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 1.1 KiB |
26
src/web.rs
|
|
@ -823,32 +823,6 @@ pub fn write_cookies(text: &str) -> Result<usize, String> {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// The web UI keeps its own hardcoded theme list (a JS `THEMES` array plus
|
|
||||||
/// per-theme CSS `.theme-*` blocks) that has to stay in sync with the
|
|
||||||
/// desktop's `theme::THEMES`. This pins that: every desktop theme must be
|
|
||||||
/// selectable *and* styled in the SPA, so newer themes can't silently go
|
|
||||||
/// missing from the web (as cemetery-moss/vampire/cyberpunk/… once did).
|
|
||||||
#[test]
|
|
||||||
fn web_ui_offers_every_desktop_theme() {
|
|
||||||
for (id, _label) in crate::theme::THEMES {
|
|
||||||
// Present in the JS picker array as `['id',…]` (label formatting
|
|
||||||
// varies — some need JS-escaping — so key on the id token).
|
|
||||||
let entry = format!("['{id}',");
|
|
||||||
assert!(
|
|
||||||
HTML_UI.contains(&entry),
|
|
||||||
"theme '{id}' missing from the web THEMES picker array (expected {entry})"
|
|
||||||
);
|
|
||||||
// Styled: `dark` is the default `:root{{…}}`, the rest are classes.
|
|
||||||
if *id != "dark" {
|
|
||||||
let css = format!(".theme-{id}{{");
|
|
||||||
assert!(
|
|
||||||
HTML_UI.contains(&css),
|
|
||||||
"theme '{id}' has no CSS block in the web UI (expected `{css}`)"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn merge_keeps_blank_password_by_url() {
|
fn merge_keeps_blank_password_by_url() {
|
||||||
use crate::config::{RemoteKind, RemoteSection};
|
use crate::config::{RemoteKind, RemoteSection};
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 7 KiB |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 21 KiB |
|
|
@ -1,31 +1,31 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||||
<!-- Catacomb app icon: a goth coffin with a play triangle punched out of
|
<!-- Catacomb app icon: a crypt archway sheltering a play wedge, in the
|
||||||
it (negative space), in the UI's default palette (bg #1a1a2e / accent
|
UI's default palette (bg #1a1a2e / accent #e94560). Full-bleed
|
||||||
#e94560). Full-bleed background with the coffin inside the central
|
background with all strokes inside the central maskable safe zone,
|
||||||
maskable safe zone, so the same art serves purpose:any and
|
so the same art serves purpose:any and purpose:maskable.
|
||||||
purpose:maskable.
|
Regenerate the PNGs after editing:
|
||||||
Regenerate every derived PNG after editing:
|
rsvg-convert -w 512 -h 512 icon.svg -o icon-512.png
|
||||||
rsvg-convert -w 512 -h 512 src/web_ui/icon.svg -o src/web_ui/icon-512.png
|
rsvg-convert -w 192 -h 192 icon.svg -o icon-192.png
|
||||||
rsvg-convert -w 192 -h 192 src/web_ui/icon.svg -o src/web_ui/icon-192.png
|
rsvg-convert -w 180 -h 180 icon.svg -o apple-touch-icon.png -->
|
||||||
rsvg-convert -w 180 -h 180 src/web_ui/icon.svg -o src/web_ui/apple-touch-icon.png
|
|
||||||
rsvg-convert -w 256 -h 256 src/web_ui/icon.svg -o icon.png # desktop window / tray / packaging -->
|
|
||||||
<defs>
|
<defs>
|
||||||
<radialGradient id="bg" cx="30%" cy="12%" r="130%">
|
<radialGradient id="bg" cx="30%" cy="12%" r="130%">
|
||||||
<stop offset="0" stop-color="#272747"/>
|
<stop offset="0" stop-color="#272747"/>
|
||||||
<stop offset="1" stop-color="#131322"/>
|
<stop offset="1" stop-color="#131322"/>
|
||||||
</radialGradient>
|
</radialGradient>
|
||||||
<linearGradient id="acc" x1="0" y1="0" x2="0" y2="1">
|
<linearGradient id="arch" x1="0" y1="0" x2="0" y2="1">
|
||||||
<stop offset="0" stop-color="#ff5f7d"/>
|
<stop offset="0" stop-color="#ff5f7d"/>
|
||||||
<stop offset="1" stop-color="#cc2b4d"/>
|
<stop offset="1" stop-color="#cc2b4d"/>
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<!-- punch the play wedge out of the solid coffin so the bg shows through -->
|
|
||||||
<mask id="cut">
|
|
||||||
<rect width="512" height="512" fill="white"/>
|
|
||||||
<polygon points="230,206 230,308 312,257" fill="black"/>
|
|
||||||
</mask>
|
|
||||||
</defs>
|
</defs>
|
||||||
<rect width="512" height="512" fill="url(#bg)"/>
|
<rect width="512" height="512" fill="url(#bg)"/>
|
||||||
<!-- hexagonal coffin: flat head edge, sharp shoulder, long taper, flat foot -->
|
<!-- doorway interior: faint accent glow so the opening reads as depth -->
|
||||||
<polygon points="206,104 306,104 354,172 314,426 198,426 158,172"
|
<path d="M192 396 V240 a64 64 0 0 1 128 0 V396 Z" fill="#e94560" opacity="0.16"/>
|
||||||
fill="url(#acc)" mask="url(#cut)"/>
|
<!-- arch -->
|
||||||
|
<path d="M154 396 V240 a102 102 0 0 1 204 0 V396" fill="none"
|
||||||
|
stroke="url(#arch)" stroke-width="28" stroke-linecap="round"/>
|
||||||
|
<!-- floor (solid colour: a zero-height bounding box degenerates the
|
||||||
|
objectBoundingBox gradient and rsvg drops the stroke entirely) -->
|
||||||
|
<path d="M118 396 H394" stroke="#cc2b4d" stroke-width="28" stroke-linecap="round"/>
|
||||||
|
<!-- play wedge in the doorway -->
|
||||||
|
<path d="M233 282 l60 34 -60 34 Z" fill="#ff5f7d"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
|
@ -25,19 +25,6 @@
|
||||||
.theme-emo-nocturnal{--bg:#0a0a0a;--panel:#0d0d0d;--card:#1a1a1a;--accent:#ff0090;--text:#e8e8e8;--muted:#888;--border:#2a2a2a}
|
.theme-emo-nocturnal{--bg:#0a0a0a;--panel:#0d0d0d;--card:#1a1a1a;--accent:#ff0090;--text:#e8e8e8;--muted:#888;--border:#2a2a2a}
|
||||||
.theme-emo-coffin{--bg:#0d0009;--panel:#110010;--card:#1a0018;--accent:#cc2222;--text:#c0c0c0;--muted:#666;--border:#3a0030}
|
.theme-emo-coffin{--bg:#0d0009;--panel:#110010;--card:#1a0018;--accent:#cc2222;--text:#c0c0c0;--muted:#666;--border:#3a0030}
|
||||||
.theme-emo-scene-queen{--bg:#080818;--panel:#0a0a1e;--card:#111128;--accent:#39ff14;--text:#c8c8ff;--muted:#666;--border:#222244}
|
.theme-emo-scene-queen{--bg:#080818;--panel:#0a0a1e;--card:#111128;--accent:#39ff14;--text:#c8c8ff;--muted:#666;--border:#222244}
|
||||||
/* Palettes ported from the desktop theme.rs (Catacomb/goth, neon, dev, cozy).
|
|
||||||
Keep this list in sync with theme::THEMES — web::tests::web_ui_offers_every_desktop_theme guards it. */
|
|
||||||
.theme-cemetery-moss{--bg:#1a1f1a;--panel:#1e241e;--card:#242a24;--accent:#7a8a6a;--text:#b8b8a8;--muted:#8a8a7a;--border:#363a30}
|
|
||||||
.theme-vampire{--bg:#0d0006;--panel:#120008;--card:#18000c;--accent:#c9a227;--text:#d8c8a8;--muted:#9a8a6a;--border:#3a0014}
|
|
||||||
.theme-witching-hour{--bg:#0a0a1f;--panel:#0e0e28;--card:#14142e;--accent:#8f77c0;--text:#c0c8e0;--muted:#8a90b0;--border:#2a2a55}
|
|
||||||
.theme-cyberpunk{--bg:#0a0a12;--panel:#0e0e18;--card:#14141f;--accent:#00fff5;--text:#c8c8e0;--muted:#7a7a95;--border:#2a2a3a}
|
|
||||||
.theme-synthwave{--bg:#2b0a3d;--panel:#330a48;--card:#3a1055;--accent:#ff2a6d;--text:#e8c0e0;--muted:#a98ac0;--border:#5a1a7a}
|
|
||||||
.theme-vaporwave{--bg:#1a0033;--panel:#200040;--card:#28004e;--accent:#ff71ce;--text:#e0c0ff;--muted:#a986cc;--border:#4a0070}
|
|
||||||
.theme-gruvbox{--bg:#282828;--panel:#32302f;--card:#3c3836;--accent:#fe8019;--text:#ebdbb2;--muted:#a89984;--border:#504945}
|
|
||||||
.theme-tokyo-night{--bg:#1a1b26;--panel:#20202f;--card:#24253a;--accent:#7aa2f7;--text:#c0caf5;--muted:#787c99;--border:#3b4261}
|
|
||||||
.theme-paper{--bg:#f4ecd8;--panel:#f8f0dc;--card:#efe5cc;--accent:#8b6f3a;--text:#3d2b1f;--muted:#8a7a5a;--border:#c4a86a}
|
|
||||||
.theme-honey{--bg:#fff4e0;--panel:#fff9ec;--card:#ffecc8;--accent:#c78820;--text:#5c3818;--muted:#a8834a;--border:#e2c48a}
|
|
||||||
.theme-candlelight{--bg:#f2e6d0;--panel:#f6ecd9;--card:#ead8b8;--accent:#a8703a;--text:#3a2818;--muted:#8a6a48;--border:#c8a06a}
|
|
||||||
*{box-sizing:border-box;margin:0;padding:0}
|
*{box-sizing:border-box;margin:0;padding:0}
|
||||||
body{background:var(--bg);color:var(--text);font:14px/1.5 system-ui,sans-serif;display:flex;flex-direction:column;height:100dvh;min-height:100dvh;overflow:hidden;overscroll-behavior:none}
|
body{background:var(--bg);color:var(--text);font:14px/1.5 system-ui,sans-serif;display:flex;flex-direction:column;height:100dvh;min-height:100dvh;overflow:hidden;overscroll-behavior:none}
|
||||||
header{background:var(--panel);padding:8px 12px;display:flex;gap:8px;align-items:center;border-bottom:1px solid var(--border);flex-shrink:0;flex-wrap:wrap;position:sticky;top:0;z-index:20}
|
header{background:var(--panel);padding:8px 12px;display:flex;gap:8px;align-items:center;border-bottom:1px solid var(--border);flex-shrink:0;flex-wrap:wrap;position:sticky;top:0;z-index:20}
|
||||||
|
|
@ -2413,9 +2400,7 @@ async function showMetadata(id){
|
||||||
function switchMetaTab(tab,which){tab.parentElement.querySelectorAll('.meta-tab').forEach(t=>t.classList.remove('active'));tab.classList.add('active');document.getElementById('meta-summary').style.display=which==='summary'?'':'none';document.getElementById('meta-raw').style.display=which==='raw'?'':'none'}
|
function switchMetaTab(tab,which){tab.parentElement.querySelectorAll('.meta-tab').forEach(t=>t.classList.remove('active'));tab.classList.add('active');document.getElementById('meta-summary').style.display=which==='summary'?'':'none';document.getElementById('meta-raw').style.display=which==='raw'?'':'none'}
|
||||||
|
|
||||||
/* ── Settings ───────────────────────────────────────────────────── */
|
/* ── Settings ───────────────────────────────────────────────────── */
|
||||||
// Keep in sync with the desktop theme::THEMES (web::tests::web_ui_offers_every_desktop_theme
|
const THEMES=[['dark','Dark'],['light','Light'],['dracula','Dracula'],['trans','Trans'],['emo-nocturnal','Emo: Nocturnal'],['emo-coffin','Emo: Coffin'],['emo-scene-queen','Emo: Scene Queen'],['solarized','Solarized'],['nord','Nord'],['amoled','AMOLED']];
|
||||||
// guards it). solarized + amoled are web-only extras kept at the end.
|
|
||||||
const THEMES=[['dark','Dark'],['light','Light'],['dracula','Dracula'],['trans','Trans'],['emo-nocturnal','Emo: Nocturnal'],['emo-coffin','Emo: Coffin'],['emo-scene-queen','Emo: Scene Queen'],['cemetery-moss','Cemetery Moss'],['vampire','Vampire'],['witching-hour','Witching Hour'],['cyberpunk','Cyberpunk'],['synthwave',"Synthwave '84"],['vaporwave','Vaporwave'],['nord','Nord'],['gruvbox','Gruvbox'],['tokyo-night','Tokyo Night'],['paper','Paper'],['honey','Honey'],['candlelight','Candlelight'],['solarized','Solarized'],['amoled','AMOLED']];
|
|
||||||
function applyTheme(t){document.body.className=t==='dark'?'':'theme-'+t;localStorage.setItem('theme',t);
|
function applyTheme(t){document.body.className=t==='dark'?'':'theme-'+t;localStorage.setItem('theme',t);
|
||||||
// Keep the browser/OS chrome (Android status bar, installed-PWA title bar)
|
// Keep the browser/OS chrome (Android status bar, installed-PWA title bar)
|
||||||
// in step with the active theme's panel colour.
|
// in step with the active theme's panel colour.
|
||||||
|
|
|
||||||