huge changes

This commit is contained in:
Supertiger
2025-08-01 18:04:20 +01:00
parent 441836bdd3
commit 382f61a615
25 changed files with 9699 additions and 17 deletions

View File

@@ -4,10 +4,13 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link href="/fontawesome/css/fontawesome.min.css" rel="stylesheet" />
<link href="/fontawesome/css/solid.min.css" rel="stylesheet" />
<title>Vite + TS</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@@ -11,5 +11,10 @@
"devDependencies": {
"typescript": "~5.8.3",
"vite": "^7.0.4"
},
"dependencies": {
"@adobe/css-tools": "^4.4.3",
"@tbela99/css-parser": "^1.1.1",
"jsonc-parser": "^3.3.1"
}
}

147
pnpm-lock.yaml generated
View File

@@ -7,16 +7,29 @@ settings:
importers:
.:
dependencies:
'@adobe/css-tools':
specifier: ^4.4.3
version: 4.4.3
'@tbela99/css-parser':
specifier: ^1.1.1
version: 1.1.1
jsonc-parser:
specifier: ^3.3.1
version: 3.3.1
devDependencies:
typescript:
specifier: ~5.8.3
version: 5.8.3
vite:
specifier: ^7.0.4
version: 7.0.6
version: 7.0.6(lightningcss@1.30.1)
packages:
'@adobe/css-tools@4.4.3':
resolution: {integrity: sha512-VQKMkwriZbaOgVCby1UDY/LDk5fIjhQicCvVPFqfe+69fWaPWydbWJ3wRt59/YzIwda1I81loas3oCoHxnqvdA==}
'@esbuild/aix-ppc64@0.25.8':
resolution: {integrity: sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==}
engines: {node: '>=18'}
@@ -273,9 +286,16 @@ packages:
cpu: [x64]
os: [win32]
'@tbela99/css-parser@1.1.1':
resolution: {integrity: sha512-LYHrxZ//mJlsb1R43dYIZNgN917LCh9OtPJi+02T2jYSXvPmVFAFOHu38HF6XPFeIr3ws8BhcoGi9XvD5Jdi9g==}
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
detect-libc@2.0.4:
resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
engines: {node: '>=8'}
esbuild@0.25.8:
resolution: {integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==}
engines: {node: '>=18'}
@@ -294,6 +314,73 @@ packages:
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
jsonc-parser@3.3.1:
resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==}
lightningcss-darwin-arm64@1.30.1:
resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [darwin]
lightningcss-darwin-x64@1.30.1:
resolution: {integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [darwin]
lightningcss-freebsd-x64@1.30.1:
resolution: {integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [freebsd]
lightningcss-linux-arm-gnueabihf@1.30.1:
resolution: {integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==}
engines: {node: '>= 12.0.0'}
cpu: [arm]
os: [linux]
lightningcss-linux-arm64-gnu@1.30.1:
resolution: {integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
lightningcss-linux-arm64-musl@1.30.1:
resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
lightningcss-linux-x64-gnu@1.30.1:
resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
lightningcss-linux-x64-musl@1.30.1:
resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
lightningcss-win32-arm64-msvc@1.30.1:
resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [win32]
lightningcss-win32-x64-msvc@1.30.1:
resolution: {integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [win32]
lightningcss@1.30.1:
resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==}
engines: {node: '>= 12.0.0'}
nanoid@3.3.11:
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
@@ -370,6 +457,8 @@ packages:
snapshots:
'@adobe/css-tools@4.4.3': {}
'@esbuild/aix-ppc64@0.25.8':
optional: true
@@ -508,8 +597,13 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.46.2':
optional: true
'@tbela99/css-parser@1.1.1': {}
'@types/estree@1.0.8': {}
detect-libc@2.0.4:
optional: true
esbuild@0.25.8:
optionalDependencies:
'@esbuild/aix-ppc64': 0.25.8
@@ -546,6 +640,54 @@ snapshots:
fsevents@2.3.3:
optional: true
jsonc-parser@3.3.1: {}
lightningcss-darwin-arm64@1.30.1:
optional: true
lightningcss-darwin-x64@1.30.1:
optional: true
lightningcss-freebsd-x64@1.30.1:
optional: true
lightningcss-linux-arm-gnueabihf@1.30.1:
optional: true
lightningcss-linux-arm64-gnu@1.30.1:
optional: true
lightningcss-linux-arm64-musl@1.30.1:
optional: true
lightningcss-linux-x64-gnu@1.30.1:
optional: true
lightningcss-linux-x64-musl@1.30.1:
optional: true
lightningcss-win32-arm64-msvc@1.30.1:
optional: true
lightningcss-win32-x64-msvc@1.30.1:
optional: true
lightningcss@1.30.1:
dependencies:
detect-libc: 2.0.4
optionalDependencies:
lightningcss-darwin-arm64: 1.30.1
lightningcss-darwin-x64: 1.30.1
lightningcss-freebsd-x64: 1.30.1
lightningcss-linux-arm-gnueabihf: 1.30.1
lightningcss-linux-arm64-gnu: 1.30.1
lightningcss-linux-arm64-musl: 1.30.1
lightningcss-linux-x64-gnu: 1.30.1
lightningcss-linux-x64-musl: 1.30.1
lightningcss-win32-arm64-msvc: 1.30.1
lightningcss-win32-x64-msvc: 1.30.1
optional: true
nanoid@3.3.11: {}
picocolors@1.1.1: {}
@@ -593,7 +735,7 @@ snapshots:
typescript@5.8.3: {}
vite@7.0.6:
vite@7.0.6(lightningcss@1.30.1):
dependencies:
esbuild: 0.25.8
fdir: 6.4.6(picomatch@4.0.3)
@@ -603,3 +745,4 @@ snapshots:
tinyglobby: 0.2.14
optionalDependencies:
fsevents: 2.3.3
lightningcss: 1.30.1

BIN
public/bg.avif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 KiB

8349
public/fontawesome/css/fontawesome.css vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

32
public/fontawesome/css/solid.css vendored Normal file
View File

@@ -0,0 +1,32 @@
/*!
* Font Awesome Free 7.0.0 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2025 Fonticons, Inc.
*/
:root,
:host {
--fa-family-classic: "Font Awesome 7 Free";
--fa-font-solid: normal 900 1em/1 var(--fa-family-classic);
/* deprecated: this older custom property will be removed next major release */
--fa-style-family-classic: var(--fa-family-classic);
}
@font-face {
font-family: "Font Awesome 7 Free";
font-style: normal;
font-weight: 900;
font-display: block;
src: url("../webfonts/fa-solid-900.woff2");
}
.fas {
--fa-family: var(--fa-family-classic);
--fa-style: 900;
}
.fa-classic {
--fa-family: var(--fa-family-classic);
}
.fa-solid {
--fa-style: 900;
}

6
public/fontawesome/css/solid.min.css vendored Normal file
View File

@@ -0,0 +1,6 @@
/*!
* Font Awesome Free 7.0.0 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2025 Fonticons, Inc.
*/
:host,:root{--fa-family-classic:"Font Awesome 7 Free";--fa-font-solid:normal 900 1em/1 var(--fa-family-classic);--fa-style-family-classic:var(--fa-family-classic)}@font-face{font-family:"Font Awesome 7 Free";font-style:normal;font-weight:900;font-display:block;src:url(../webfonts/fa-solid-900.woff2)}.fas{--fa-style:900}.fa-classic,.fas{--fa-family:var(--fa-family-classic)}.fa-solid{--fa-style:900}

Binary file not shown.

View File

@@ -0,0 +1,219 @@
// -*- mode: jsonc -*-
{
// "layer": "top", // Waybar at top layer
// "position": "bottom", // Waybar position (top|bottom|left|right)
"height": 30, // Waybar height (to be removed for auto height)
// "width": 1280, // Waybar width
"spacing": 4, // Gaps between modules (4px)
// Choose the order of the modules
"modules-left": [
"sway/workspaces",
"sway/mode",
"sway/scratchpad",
"custom/media"
],
"modules-center": [
"sway/window"
],
"modules-right": [
"mpd",
"idle_inhibitor",
"pulseaudio",
"network",
"power-profiles-daemon",
"cpu",
"memory",
"temperature",
"backlight",
"keyboard-state",
"sway/language",
"battery",
"battery#bat2",
"clock",
"tray",
"custom/power"
],
// Modules configuration
// "sway/workspaces": {
// "disable-scroll": true,
// "all-outputs": true,
// "warp-on-scroll": false,
// "format": "{name}: {icon}",
// "format-icons": {
// "1": "",
// "2": "",
// "3": "",
// "4": "",
// "5": "",
// "urgent": "",
// "focused": "",
// "default": ""
// }
// },
"keyboard-state": {
"numlock": true,
"capslock": true,
"format": "{name} {icon}",
"format-icons": {
"locked": "",
"unlocked": ""
}
},
"sway/mode": {
"format": "<span style=\"italic\">{}</span>"
},
"sway/scratchpad": {
"format": "{icon} {count}",
"show-empty": false,
"format-icons": ["", ""],
"tooltip": true,
"tooltip-format": "{app}: {title}"
},
"mpd": {
"format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ⸨{songPosition}|{queueLength}⸩ {volume}% ",
"format-disconnected": "Disconnected ",
"format-stopped": "{consumeIcon}{randomIcon}{repeatIcon}{singleIcon}Stopped ",
"unknown-tag": "N/A",
"interval": 5,
"consume-icons": {
"on": " "
},
"random-icons": {
"off": "<span color=\"#f53c3c\"></span> ",
"on": " "
},
"repeat-icons": {
"on": " "
},
"single-icons": {
"on": "1 "
},
"state-icons": {
"paused": "",
"playing": ""
},
"tooltip-format": "MPD (connected)",
"tooltip-format-disconnected": "MPD (disconnected)"
},
"idle_inhibitor": {
"format": "{icon}",
"format-icons": {
"activated": "",
"deactivated": ""
}
},
"tray": {
// "icon-size": 21,
"spacing": 10,
// "icons": {
// "blueman": "bluetooth",
// "TelegramDesktop": "$HOME/.local/share/icons/hicolor/16x16/apps/telegram.png"
// }
},
"clock": {
// "timezone": "America/New_York",
"tooltip-format": "<big>{:%Y %B}</big>\n<tt><small>{calendar}</small></tt>",
"format-alt": "{:%Y-%m-%d}"
},
"cpu": {
"format": "{usage}% ",
"tooltip": false
},
"memory": {
"format": "{}% "
},
"temperature": {
// "thermal-zone": 2,
// "hwmon-path": "/sys/class/hwmon/hwmon2/temp1_input",
"critical-threshold": 80,
// "format-critical": "{temperatureC}°C {icon}",
"format": "{temperatureC}°C {icon}",
"format-icons": ["", "", ""]
},
"backlight": {
// "device": "acpi_video1",
"format": "{percent}% {icon}",
"format-icons": ["", "", "", "", "", "", "", "", ""]
},
"battery": {
"states": {
// "good": 95,
"warning": 30,
"critical": 15
},
"format": "{capacity}% {icon}",
"format-full": "{capacity}% {icon}",
"format-charging": "{capacity}% ",
"format-plugged": "{capacity}% ",
"format-alt": "{time} {icon}",
// "format-good": "", // An empty format will hide the module
// "format-full": "",
"format-icons": ["", "", "", "", ""]
},
"battery#bat2": {
"bat": "BAT2"
},
"power-profiles-daemon": {
"format": "{icon}",
"tooltip-format": "Power profile: {profile}\nDriver: {driver}",
"tooltip": true,
"format-icons": {
"default": "",
"performance": "",
"balanced": "",
"power-saver": ""
}
},
"network": {
// "interface": "wlp2*", // (Optional) To force the use of this interface
"format-wifi": "{essid} ({signalStrength}%) ",
"format-ethernet": "{ipaddr}/{cidr} ",
"tooltip-format": "{ifname} via {gwaddr} ",
"format-linked": "{ifname} (No IP) ",
"format-disconnected": "Disconnected ⚠",
"format-alt": "{ifname}: {ipaddr}/{cidr}"
},
"pulseaudio": {
// "scroll-step": 1, // %, can be a float
"format": "{volume}% {icon} {format_source}",
"format-bluetooth": "{volume}% {icon} {format_source}",
"format-bluetooth-muted": " {icon} {format_source}",
"format-muted": " {format_source}",
"format-source": "{volume}% ",
"format-source-muted": "",
"format-icons": {
"headphone": "",
"hands-free": "",
"headset": "",
"phone": "",
"portable": "",
"car": "",
"default": ["", "", ""]
},
"on-click": "pavucontrol"
},
"custom/media": {
"format": "{icon} {text}",
"return-type": "json",
"max-length": 40,
"format-icons": {
"spotify": "",
"default": "🎜"
},
"escape": true,
"exec": "$HOME/.config/waybar/mediaplayer.py 2> /dev/null" // Script in resources folder
// "exec": "$HOME/.config/waybar/mediaplayer.py --player spotify 2> /dev/null" // Filter player based on name
},
"custom/power": {
"format" : "⏻ ",
"tooltip": false,
"menu": "on-click",
"menu-file": "$HOME/.config/waybar/power_menu.xml", // Menu file in resources folder
"menu-actions": {
"shutdown": "shutdown",
"reboot": "reboot",
"suspend": "systemctl suspend",
"hibernate": "systemctl hibernate"
}
}
}

327
public/resources/style.css Normal file
View File

@@ -0,0 +1,327 @@
* {
/* `otf-font-awesome` is required to be installed for icons */
font-family: FontAwesome, Roboto, Helvetica, Arial, sans-serif;
font-size: 13px;
}
window#waybar {
background-color: rgba(43, 48, 59, 0.5);
border-bottom: 3px solid rgba(100, 114, 125, 0.5);
color: #ffffff;
transition-property: background-color;
transition-duration: .5s;
}
window#waybar.hidden {
opacity: 0.2;
}
/*
window#waybar.empty {
background-color: transparent;
}
window#waybar.solo {
background-color: #FFFFFF;
}
*/
window#waybar.termite {
background-color: #3F3F3F;
}
window#waybar.chromium {
background-color: #000000;
border: none;
}
button {
/* Use box-shadow instead of border so the text isn't offset */
box-shadow: inset 0 -3px transparent;
/* Avoid rounded borders under each button name */
border: none;
border-radius: 0;
}
/* https://github.com/Alexays/Waybar/wiki/FAQ#the-workspace-buttons-have-a-strange-hover-effect */
button:hover {
background: inherit;
box-shadow: inset 0 -3px #ffffff;
}
/* you can set a style on hover for any module like this */
#pulseaudio:hover {
background-color: #a37800;
}
#workspaces button {
padding: 0 5px;
background-color: transparent;
color: #ffffff;
}
#workspaces button:hover {
background: rgba(0, 0, 0, 0.2);
}
#workspaces button.focused {
background-color: #64727D;
box-shadow: inset 0 -3px #ffffff;
}
#workspaces button.urgent {
background-color: #eb4d4b;
}
#mode {
background-color: #64727D;
box-shadow: inset 0 -3px #ffffff;
}
#clock,
#battery,
#cpu,
#memory,
#disk,
#temperature,
#backlight,
#network,
#pulseaudio,
#wireplumber,
#custom-media,
#tray,
#mode,
#idle_inhibitor,
#scratchpad,
#power-profiles-daemon,
#mpd {
padding: 0 10px;
color: #ffffff;
}
#window,
#workspaces {
margin: 0 4px;
}
/* If workspaces is the leftmost module, omit left margin */
.modules-left > widget:first-child > #workspaces {
margin-left: 0;
}
/* If workspaces is the rightmost module, omit right margin */
.modules-right > widget:last-child > #workspaces {
margin-right: 0;
}
#clock {
background-color: #64727D;
}
#battery {
background-color: #ffffff;
color: #000000;
}
#battery.charging, #battery.plugged {
color: #ffffff;
background-color: #26A65B;
}
@keyframes blink {
to {
background-color: #ffffff;
color: #000000;
}
}
/* Using steps() instead of linear as a timing function to limit cpu usage */
#battery.critical:not(.charging) {
background-color: #f53c3c;
color: #ffffff;
animation-name: blink;
animation-duration: 0.5s;
animation-timing-function: steps(12);
animation-iteration-count: infinite;
animation-direction: alternate;
}
#power-profiles-daemon {
padding-right: 15px;
}
#power-profiles-daemon.performance {
background-color: #f53c3c;
color: #ffffff;
}
#power-profiles-daemon.balanced {
background-color: #2980b9;
color: #ffffff;
}
#power-profiles-daemon.power-saver {
background-color: #2ecc71;
color: #000000;
}
label:focus {
background-color: #000000;
}
#cpu {
background-color: #2ecc71;
color: #000000;
}
#memory {
background-color: #9b59b6;
}
#disk {
background-color: #964B00;
}
#backlight {
background-color: #90b1b1;
}
#network {
background-color: #2980b9;
}
#network.disconnected {
background-color: #f53c3c;
}
#pulseaudio {
background-color: #f1c40f;
color: #000000;
}
#pulseaudio.muted {
background-color: #90b1b1;
color: #2a5c45;
}
#wireplumber {
background-color: #fff0f5;
color: #000000;
}
#wireplumber.muted {
background-color: #f53c3c;
}
#custom-media {
background-color: #66cc99;
color: #2a5c45;
min-width: 100px;
}
#custom-media.custom-spotify {
background-color: #66cc99;
}
#custom-media.custom-vlc {
background-color: #ffa000;
}
#temperature {
background-color: #f0932b;
}
#temperature.critical {
background-color: #eb4d4b;
}
#tray {
background-color: #2980b9;
}
#tray > .passive {
-gtk-icon-effect: dim;
}
#tray > .needs-attention {
-gtk-icon-effect: highlight;
background-color: #eb4d4b;
}
#idle_inhibitor {
background-color: #2d3436;
}
#idle_inhibitor.activated {
background-color: #ecf0f1;
color: #2d3436;
}
#mpd {
background-color: #66cc99;
color: #2a5c45;
}
#mpd.disconnected {
background-color: #f53c3c;
}
#mpd.stopped {
background-color: #90b1b1;
}
#mpd.paused {
background-color: #51a37a;
}
#language {
background: #00b093;
color: #740864;
padding: 0 5px;
margin: 0 5px;
min-width: 16px;
}
#keyboard-state {
background: #97e1ad;
color: #000000;
padding: 0 0px;
margin: 0 5px;
min-width: 16px;
}
#keyboard-state > label {
padding: 0 5px;
}
#keyboard-state > label.locked {
background: rgba(0, 0, 0, 0.2);
}
#scratchpad {
background: rgba(0, 0, 0, 0.2);
}
#scratchpad.empty {
background-color: transparent;
}
#privacy {
padding: 0;
}
#privacy-item {
padding: 0 5px;
color: white;
}
#privacy-item.screenshare {
background-color: #cf5700;
}
#privacy-item.audio-in {
background-color: #1ca000;
}
#privacy-item.audio-out {
background-color: #0069d4;
}

View File

@@ -1,10 +1,9 @@
import { createRootPage } from "./routes/root";
import { createWaybarPage } from "./routes/waybar";
if (location.pathname === "/waybar") {
createWaybarPage();
import("./routes/waybar/waybar").then(({ createWaybarPage }) =>
createWaybarPage()
);
}
if (location.pathname === "/") {
createRootPage();
import("./routes/root/root").then(({ createRootPage }) => createRootPage());
}

View File

@@ -1,5 +0,0 @@
const appElement = document.getElementById("app")!;
export const createRootPage = async () => {
appElement.innerHTML = "test";
};

14
src/routes/root/root.css Normal file
View File

@@ -0,0 +1,14 @@
body {
background: url(/bg.avif) no-repeat center center fixed;
background-size: cover;
}
html,
body {
height: 100%;
padding: 0;
margin: 0;
}
#waybar-iframe {
width: 100vw;
border: none;
}

8
src/routes/root/root.ts Normal file
View File

@@ -0,0 +1,8 @@
import "./root.css";
const appElement = document.getElementById("app")!;
export const createRootPage = async () => {
appElement.innerHTML = `
<iframe id="waybar-iframe" src="/waybar"></iframe>
`;
};

View File

@@ -1,5 +0,0 @@
const appElement = document.getElementById("app")!;
export const createWaybarPage = async () => {
appElement.innerHTML = "test";
};

View File

@@ -0,0 +1,61 @@
import { clamp, replaceTextWithUnicode } from "./utils";
interface LabelOpts {
config?: {
[key: string]: any;
format?: string;
"format-icons"?: string[];
};
interval?: number;
update: () => void;
}
export const createLabel = (opts: LabelOpts) => {
let format = "";
const getMarkup = () => {
if (!opts.config) return undefined;
if (!format) {
return opts.config?.format || undefined;
}
return opts.config["format-" + format] || undefined;
};
const element = document.createElement("span");
if (opts.interval && opts.update) {
setInterval(opts.update, opts.interval);
}
const set = (data: Record<string, any>) => {
const markup = getMarkup() as string | undefined;
if (!markup) {
console.error("No format found");
return "N/A";
}
const result = markup.replace(/\{(\w+)\}/g, (match, key) => {
return data[key] !== undefined ? data[key] : match;
});
element.innerHTML = replaceTextWithUnicode(result);
};
const getIcon = (percentage: number, icon?: string, max: number = 0) => {
const icons = opts.config?.["format-icons"];
const iconCount = icons?.length || 0;
const index = clamp(
percentage / ((max == 0 ? 100 : max) / iconCount),
0,
iconCount - 1
);
const resIcon = icons?.[Math.round(index)];
if (!resIcon) return "";
return resIcon;
};
return {
element,
set,
getIcon,
};
};

View File

@@ -0,0 +1,288 @@
import { parse } from "jsonc-parser";
/**
* A general interface for icon mappings, where a key maps to a single icon string or an array of icon strings.
*/
interface IconMap {
[key: string]: string | string[];
}
/**
* Common configuration properties for many Waybar modules.
*/
interface BaseModule {
format?: string;
"format-icons"?: IconMap;
tooltip?: boolean;
}
/**
* Defines the states and icons for a module.
*/
interface ModuleWithStates extends BaseModule {
states?: {
[state: string]: number;
};
"format-critical"?: string;
"format-alt"?: string;
}
/**
* Interface for the `sway/workspaces` module.
* Note: This module is commented out in the original JSON, but the interface is included for completeness.
*/
interface SwayWorkspacesModule extends BaseModule {
"disable-scroll"?: boolean;
"all-outputs"?: boolean;
"warp-on-scroll"?: boolean;
"format-icons"?: {
[key: string]: string;
};
}
/**
* Interface for the `keyboard-state` module.
*/
interface KeyboardStateModule extends BaseModule {
numlock?: boolean;
capslock?: boolean;
"format-icons"?: {
locked: string;
unlocked: string;
};
}
/**
* Interface for the `sway/mode` module.
*/
interface SwayModeModule {
format?: string;
}
/**
* Interface for the `sway/scratchpad` module.
*/
interface SwayScratchpadModule extends BaseModule {
"show-empty"?: boolean;
"format-icons"?: string[];
"tooltip-format"?: string;
}
/**
* Interface for the `mpd` module.
*/
interface MpdModule extends BaseModule {
"format-disconnected"?: string;
"format-stopped"?: string;
"unknown-tag"?: string;
interval?: number;
"consume-icons"?: {
on: string;
};
"random-icons"?: {
on: string;
off: string;
};
"repeat-icons"?: {
on: string;
};
"single-icons"?: {
on: string;
};
"state-icons"?: {
paused: string;
playing: string;
};
"tooltip-format"?: string;
"tooltip-format-disconnected"?: string;
}
/**
* Interface for the `idle_inhibitor` module.
*/
interface IdleInhibitorModule extends BaseModule {
"format-icons"?: {
activated: string;
deactivated: string;
};
}
/**
* Interface for the `tray` module.
*/
interface TrayModule {
"icon-size"?: number;
spacing?: number;
icons?: {
[appName: string]: string;
};
}
/**
* Interface for the `clock` module.
*/
interface ClockModule {
timezone?: string;
"tooltip-format"?: string;
"format-alt"?: string;
}
/**
* Interface for the `cpu` module.
*/
interface CpuModule extends BaseModule {
tooltip?: boolean;
}
/**
* Interface for the `memory` module.
*/
interface MemoryModule extends BaseModule {}
/**
* Interface for the `temperature` module.
*/
interface TemperatureModule extends ModuleWithStates {
"thermal-zone"?: number;
"hwmon-path"?: string;
"critical-threshold"?: number;
"format-icons"?: string[];
}
/**
* Interface for the `backlight` module.
*/
interface BacklightModule extends BaseModule {
device?: string;
"format-icons"?: string[];
}
/**
* Interface for the `battery` module.
*/
interface BatteryModule extends ModuleWithStates {
bat?: string; // Used for battery#bat2
"format-full"?: string;
"format-charging"?: string;
"format-plugged"?: string;
"format-good"?: string;
"format-icons"?: string[];
}
/**
* Interface for the `power-profiles-daemon` module.
*/
interface PowerProfilesDaemonModule extends BaseModule {
tooltip?: boolean;
"tooltip-format"?: string;
"format-icons"?: {
default: string;
performance: string;
balanced: string;
"power-saver": string;
};
}
/**
* Interface for the `network` module.
*/
interface NetworkModule {
interface?: string;
"format-wifi"?: string;
"format-ethernet"?: string;
"tooltip-format"?: string;
"format-linked"?: string;
"format-disconnected"?: string;
"format-alt"?: string;
}
/**
* Interface for the `pulseaudio` module.
*/
interface PulseaudioModule extends BaseModule {
"scroll-step"?: number;
"format-bluetooth"?: string;
"format-bluetooth-muted"?: string;
"format-muted"?: string;
"format-source"?: string;
"format-source-muted"?: string;
"format-icons"?: {
headphone: string;
"hands-free": string;
headset: string;
phone: string;
portable: string;
car: string;
default: string[];
};
"on-click"?: string;
}
/**
* Interface for the `custom/media` module.
*/
interface CustomMediaModule extends BaseModule {
"return-type"?: "json" | "text";
"max-length"?: number;
"format-icons"?: {
[key: string]: string;
};
escape?: boolean;
exec?: string;
}
/**
* Interface for the `custom/power` module.
*/
interface CustomPowerModule extends BaseModule {
menu?: string;
"menu-file"?: string;
"menu-actions"?: {
shutdown: string;
reboot: string;
suspend: string;
hibernate: string;
};
}
/**
* The main interface for the entire Waybar configuration file.
* It combines the main settings with a generic index signature for module configurations.
*/
export interface WaybarConfig {
layer?: "top" | "bottom" | "overlay";
position?: "top" | "bottom" | "left" | "right";
height?: number;
width?: number;
spacing?: number;
"modules-left"?: string[];
"modules-center"?: string[];
"modules-right"?: string[];
// Module-specific configurations
"sway/workspaces"?: SwayWorkspacesModule;
"sway/mode"?: SwayModeModule;
"sway/scratchpad"?: SwayScratchpadModule;
"keyboard-state"?: KeyboardStateModule;
mpd?: MpdModule;
idle_inhibitor?: IdleInhibitorModule;
tray?: TrayModule;
clock?: ClockModule;
cpu?: CpuModule;
memory?: MemoryModule;
temperature?: TemperatureModule;
backlight?: BacklightModule;
battery?: BatteryModule;
"battery#bat2"?: BatteryModule;
"power-profiles-daemon"?: PowerProfilesDaemonModule;
network?: NetworkModule;
pulseaudio?: PulseaudioModule;
"custom/media"?: CustomMediaModule;
"custom/power"?: CustomPowerModule;
}
export const parseConfig = async () => {
const response = await fetch("/resources/config.jsonc");
const jsoncString = await response.text();
return parse(jsoncString) as WaybarConfig;
};

View File

@@ -0,0 +1,11 @@
export type Module = ReturnType<typeof createModule>;
export const createModule = (name: string) => {
const element = document.createElement("div");
element.id = name;
element.className = "module";
return {
element,
};
};

View File

@@ -0,0 +1,13 @@
export type Modules = keyof typeof modules;
export type ModuleArray = Array<keyof typeof modules>;
export const modules = {
clock: () =>
import("./modules/clock").then(
({ createClockModule }) => createClockModule
),
battery: () =>
import("./modules/battery").then(
({ createBatteryModule }) => createBatteryModule
),
};

View File

@@ -0,0 +1,50 @@
import type { WaybarConfig } from "../configParser";
import type { Module } from "../createModule";
import { createLabel } from "../Label";
export const createBatteryModule = (
module: Module,
config: WaybarConfig["battery"]
) => {
const label = createLabel({
interval: 1000,
update: () => update(),
config: config!,
});
module.element.appendChild(label.element);
const update = async () => {
// const battery = await navigator.getBattery();
const battery = {
level: Math.random(),
charging: Math.random() > 0.5,
};
let state = "Unknown";
if (battery.charging && battery.level < 1) {
state = "Charging";
}
if (battery.charging && battery.level === 1) {
state = "Full";
}
const charging = battery.charging;
const batteryPercent = Math.round(battery.level * 100);
if (charging) {
module.element.classList.add("charging");
} else {
module.element.classList.remove("charging");
}
label.set({
capacity: batteryPercent,
icon: label.getIcon(batteryPercent),
});
};
update();
};

View File

@@ -0,0 +1,14 @@
import type { Module } from "../createModule";
export const createClockModule = (module: Module) => {
const update = () => {
const date = new Date();
const hours = date.getHours().toString().padStart(2, "0");
const minutes = date.getMinutes().toString().padStart(2, "0");
module.element.innerHTML = `${hours}:${minutes}`;
};
update();
setInterval(update, 1000);
};

View File

@@ -0,0 +1,60 @@
import { CssTypes, parse, stringify } from "@adobe/css-tools";
export const clamp = (val: number, min: number, max: number) =>
Math.min(Math.max(val, min), max);
const fetchFontAwesomeStylesheet = async () => {
const raw = await fetch("/fontawesome/css/fontawesome.min.css").then((res) =>
res.text()
);
return raw;
};
export const tryCatch = (tryTo: () => string, ifCatch: string) => {
try {
return tryTo();
} catch (e) {
return ifCatch;
}
};
const css = await fetchFontAwesomeStylesheet();
function unicodeToHexMap() {
const map = new Map<string, string>();
const parsed = parse(css);
parsed.stylesheet.rules.forEach((rule) => {
if (rule.type === CssTypes.rule) {
const declaration = rule.declarations[0];
if (declaration.type === CssTypes.declaration) {
if (declaration.property !== "--fa") return;
const selector = rule.selectors[0];
const value = declaration.value.slice(1, -1);
try {
const unicodeChar = String.fromCodePoint(
parseInt(value.slice(1), 16)
);
if (!Number.isNaN(parseInt(unicodeChar))) return;
map.set(unicodeChar, value);
} catch {}
}
}
});
return map;
}
const unicodeArray = [...unicodeToHexMap().keys()];
const unicodeRegex = new RegExp(
unicodeArray.map((x) => `\\${x}`).join("|"),
"g"
);
export const replaceTextWithUnicode = (text: string) => {
let newText = text.replace(
unicodeRegex,
(char) => `<span class="fa">${char}</span>`
);
return newText;
};

View File

@@ -0,0 +1,26 @@
#waybar {
display: flex;
}
.modules {
display: flex;
}
#modules-left {
}
#modules-center {
flex: 1;
}
#modules-right {
}
html,
body {
padding: 0;
margin: 0;
}
.module {
display: flex;
align-items: center;
justify-content: center;
}

View File

@@ -0,0 +1,56 @@
import { parseConfig } from "./configParser";
import { createModule } from "./createModule";
import { modules, type ModuleArray, type Modules } from "./modules";
import "./waybar.css";
const appElement = document.getElementById("app")!;
export const createWaybarPage = async () => {
const styleElement = document.createElement("link");
styleElement.rel = "stylesheet";
styleElement.href = "/resources/style.css";
document.head.appendChild(styleElement);
const config = await parseConfig();
appElement.innerHTML = `
<window id="waybar" style="height: ${config.height}px;">
<div class="modules" id="modules-left" style="gap: ${config.spacing}px;"></div>
<div class="modules" id="modules-center" style="gap: ${config.spacing}px;"></div>
<div class="modules" id="modules-right" style="gap: ${config.spacing}px;"></div>
</window>`;
const modulesLeftElement = document.getElementById("modules-left")!;
const modulesCenterElement = document.getElementById("modules-center")!;
const modulesRightElement = document.getElementById("modules-right")!;
const moduleLeft = (config["modules-left"] as ModuleArray) || [];
const moduleCenter = (config["modules-center"] as ModuleArray) || [];
const moduleRight = (config["modules-right"] as ModuleArray) || [];
const loadModules = async (
enabledModules: ModuleArray,
modulesElement: HTMLElement
) => {
enabledModules.forEach(async (moduleName) => {
if (moduleName === "battery") {
console.log(moduleName);
const module = createModule("battery");
modulesElement.appendChild(module.element);
const createBatteryModule = await modules.battery();
createBatteryModule(module, config.battery);
}
if (moduleName === "clock") {
console.log(moduleName);
const module = createModule("clock");
modulesElement.appendChild(module.element);
const createClockModule = await modules.clock();
createClockModule(module, config.clock);
}
});
};
loadModules(moduleLeft, modulesLeftElement);
loadModules(moduleCenter, modulesCenterElement);
loadModules(moduleRight, modulesRightElement);
};