mirror of
https://github.com/SupertigerDev/waybar-online.git
synced 2026-03-24 16:06:37 +00:00
huge changes
This commit is contained in:
@@ -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());
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
const appElement = document.getElementById("app")!;
|
||||
|
||||
export const createRootPage = async () => {
|
||||
appElement.innerHTML = "test";
|
||||
};
|
||||
14
src/routes/root/root.css
Normal file
14
src/routes/root/root.css
Normal 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
8
src/routes/root/root.ts
Normal 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>
|
||||
`;
|
||||
};
|
||||
@@ -1,5 +0,0 @@
|
||||
const appElement = document.getElementById("app")!;
|
||||
|
||||
export const createWaybarPage = async () => {
|
||||
appElement.innerHTML = "test";
|
||||
};
|
||||
61
src/routes/waybar/Label.ts
Normal file
61
src/routes/waybar/Label.ts
Normal 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,
|
||||
};
|
||||
};
|
||||
288
src/routes/waybar/configParser.ts
Normal file
288
src/routes/waybar/configParser.ts
Normal 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;
|
||||
};
|
||||
11
src/routes/waybar/createModule.ts
Normal file
11
src/routes/waybar/createModule.ts
Normal 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,
|
||||
};
|
||||
};
|
||||
13
src/routes/waybar/modules.ts
Normal file
13
src/routes/waybar/modules.ts
Normal 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
|
||||
),
|
||||
};
|
||||
50
src/routes/waybar/modules/battery.ts
Normal file
50
src/routes/waybar/modules/battery.ts
Normal 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();
|
||||
};
|
||||
14
src/routes/waybar/modules/clock.ts
Normal file
14
src/routes/waybar/modules/clock.ts
Normal 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);
|
||||
};
|
||||
60
src/routes/waybar/utils.ts
Normal file
60
src/routes/waybar/utils.ts
Normal 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;
|
||||
};
|
||||
26
src/routes/waybar/waybar.css
Normal file
26
src/routes/waybar/waybar.css
Normal 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;
|
||||
}
|
||||
56
src/routes/waybar/waybar.ts
Normal file
56
src/routes/waybar/waybar.ts
Normal 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);
|
||||
};
|
||||
Reference in New Issue
Block a user