mirror of
https://github.com/SupertigerDev/waybar-online.git
synced 2026-03-24 08:06:37 +00:00
Add charging icon and fix errors
This commit is contained in:
@@ -5,7 +5,7 @@ interface LabelOpts {
|
|||||||
config?: {
|
config?: {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
format?: string;
|
format?: string;
|
||||||
states: string[];
|
states?: Record<string, number>;
|
||||||
"format-icons"?: string[];
|
"format-icons"?: string[];
|
||||||
};
|
};
|
||||||
module: Module;
|
module: Module;
|
||||||
@@ -20,9 +20,11 @@ export const createLabel = (opts: LabelOpts) => {
|
|||||||
if (!format) {
|
if (!format) {
|
||||||
return opts.config?.format || undefined;
|
return opts.config?.format || undefined;
|
||||||
}
|
}
|
||||||
return opts.config["format-" + format] || undefined;
|
return format;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setFormat = (value?: string) => (format = value || "format");
|
||||||
|
|
||||||
const element = document.createElement("span");
|
const element = document.createElement("span");
|
||||||
if (opts.interval && opts.update) {
|
if (opts.interval && opts.update) {
|
||||||
setInterval(opts.update, opts.interval);
|
setInterval(opts.update, opts.interval);
|
||||||
@@ -86,5 +88,6 @@ export const createLabel = (opts: LabelOpts) => {
|
|||||||
set,
|
set,
|
||||||
getIcon,
|
getIcon,
|
||||||
getState,
|
getState,
|
||||||
|
setFormat,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,284 +1,201 @@
|
|||||||
import { parse } from "jsonc-parser";
|
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 {
|
export interface WaybarConfig {
|
||||||
layer?: "top" | "bottom" | "overlay";
|
height: number;
|
||||||
position?: "top" | "bottom" | "left" | "right";
|
spacing: number;
|
||||||
height?: number;
|
"modules-left": string[];
|
||||||
width?: number;
|
"modules-center": string[];
|
||||||
spacing?: number;
|
"modules-right": string[];
|
||||||
"modules-left"?: string[];
|
"keyboard-state": KeyboardState;
|
||||||
"modules-center"?: string[];
|
"sway/mode": Memory;
|
||||||
"modules-right"?: string[];
|
"sway/scratchpad": SwayScratchpad;
|
||||||
|
mpd: Mpd;
|
||||||
|
idle_inhibitor: IdleInhibitor;
|
||||||
|
tray: Tray;
|
||||||
|
clock: Clock;
|
||||||
|
cpu: CPU;
|
||||||
|
memory: Memory;
|
||||||
|
temperature: Temperature;
|
||||||
|
backlight: Backlight;
|
||||||
|
battery: Battery;
|
||||||
|
"battery#bat2": BatteryBat2;
|
||||||
|
"power-profiles-daemon": PowerProfilesDaemon;
|
||||||
|
network: Network;
|
||||||
|
pulseaudio: Pulseaudio;
|
||||||
|
"custom/media": CustomMedia;
|
||||||
|
"custom/power": CustomPower;
|
||||||
|
}
|
||||||
|
interface Backlight {
|
||||||
|
format: string;
|
||||||
|
"format-icons": string[];
|
||||||
|
}
|
||||||
|
interface Battery {
|
||||||
|
states: States;
|
||||||
|
format: string;
|
||||||
|
"format-full": string;
|
||||||
|
"format-charging": string;
|
||||||
|
"format-plugged": string;
|
||||||
|
"format-alt": string;
|
||||||
|
"format-icons": string[];
|
||||||
|
[key: string]: any; // Allow additional properties
|
||||||
|
}
|
||||||
|
interface States {
|
||||||
|
warning: number;
|
||||||
|
critical: number;
|
||||||
|
[key: string]: number; // Allow additional properties
|
||||||
|
}
|
||||||
|
interface BatteryBat2 {
|
||||||
|
bat: string;
|
||||||
|
}
|
||||||
|
interface Clock {
|
||||||
|
"tooltip-format": string;
|
||||||
|
"format-alt": string;
|
||||||
|
}
|
||||||
|
interface CPU {
|
||||||
|
format: string;
|
||||||
|
tooltip: boolean;
|
||||||
|
}
|
||||||
|
interface CustomMedia {
|
||||||
|
format: string;
|
||||||
|
"return-type": string;
|
||||||
|
"max-length": number;
|
||||||
|
"format-icons": CustomMediaFormatIcons;
|
||||||
|
escape: boolean;
|
||||||
|
exec: string;
|
||||||
|
}
|
||||||
|
interface CustomMediaFormatIcons {
|
||||||
|
spotify: string;
|
||||||
|
default: string;
|
||||||
|
}
|
||||||
|
interface CustomPower {
|
||||||
|
format: string;
|
||||||
|
tooltip: boolean;
|
||||||
|
menu: string;
|
||||||
|
"menu-file": string;
|
||||||
|
"menu-actions": MenuActions;
|
||||||
|
}
|
||||||
|
interface MenuActions {
|
||||||
|
shutdown: string;
|
||||||
|
reboot: string;
|
||||||
|
suspend: string;
|
||||||
|
hibernate: string;
|
||||||
|
}
|
||||||
|
|
||||||
// Module-specific configurations
|
interface IdleInhibitor {
|
||||||
"sway/workspaces"?: SwayWorkspacesModule;
|
format: string;
|
||||||
"sway/mode"?: SwayModeModule;
|
"format-icons": IdleInhibitorFormatIcons;
|
||||||
"sway/scratchpad"?: SwayScratchpadModule;
|
}
|
||||||
"keyboard-state"?: KeyboardStateModule;
|
|
||||||
mpd?: MpdModule;
|
interface IdleInhibitorFormatIcons {
|
||||||
idle_inhibitor?: IdleInhibitorModule;
|
activated: string;
|
||||||
tray?: TrayModule;
|
deactivated: string;
|
||||||
clock?: ClockModule;
|
}
|
||||||
cpu?: CpuModule;
|
|
||||||
memory?: MemoryModule;
|
interface KeyboardState {
|
||||||
temperature?: TemperatureModule;
|
numlock: boolean;
|
||||||
backlight?: BacklightModule;
|
capslock: boolean;
|
||||||
battery?: BatteryModule;
|
format: string;
|
||||||
"battery#bat2"?: BatteryModule;
|
"format-icons": KeyboardStateFormatIcons;
|
||||||
"power-profiles-daemon"?: PowerProfilesDaemonModule;
|
}
|
||||||
network?: NetworkModule;
|
|
||||||
pulseaudio?: PulseaudioModule;
|
interface KeyboardStateFormatIcons {
|
||||||
"custom/media"?: CustomMediaModule;
|
locked: string;
|
||||||
"custom/power"?: CustomPowerModule;
|
unlocked: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Memory {
|
||||||
|
format: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Mpd {
|
||||||
|
format: string;
|
||||||
|
"format-disconnected": string;
|
||||||
|
"format-stopped": string;
|
||||||
|
"unknown-tag": string;
|
||||||
|
interval: number;
|
||||||
|
"consume-icons": Icons;
|
||||||
|
"random-icons": RandomIcons;
|
||||||
|
"repeat-icons": Icons;
|
||||||
|
"single-icons": Icons;
|
||||||
|
"state-icons": StateIcons;
|
||||||
|
"tooltip-format": string;
|
||||||
|
"tooltip-format-disconnected": string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Icons {
|
||||||
|
on: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RandomIcons {
|
||||||
|
off: string;
|
||||||
|
on: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StateIcons {
|
||||||
|
paused: string;
|
||||||
|
playing: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Network {
|
||||||
|
"format-wifi": string;
|
||||||
|
"format-ethernet": string;
|
||||||
|
"tooltip-format": string;
|
||||||
|
"format-linked": string;
|
||||||
|
"format-disconnected": string;
|
||||||
|
"format-alt": string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PowerProfilesDaemon {
|
||||||
|
format: string;
|
||||||
|
"tooltip-format": string;
|
||||||
|
tooltip: boolean;
|
||||||
|
"format-icons": PowerProfilesDaemonFormatIcons;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PowerProfilesDaemonFormatIcons {
|
||||||
|
default: string;
|
||||||
|
performance: string;
|
||||||
|
balanced: string;
|
||||||
|
"power-saver": string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Pulseaudio {
|
||||||
|
format: string;
|
||||||
|
"format-bluetooth": string;
|
||||||
|
"format-bluetooth-muted": string;
|
||||||
|
"format-muted": string;
|
||||||
|
"format-source": string;
|
||||||
|
"format-source-muted": string;
|
||||||
|
"format-icons": PulseaudioFormatIcons;
|
||||||
|
"on-click": string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PulseaudioFormatIcons {
|
||||||
|
headphone: string;
|
||||||
|
"hands-free": string;
|
||||||
|
headset: string;
|
||||||
|
phone: string;
|
||||||
|
portable: string;
|
||||||
|
car: string;
|
||||||
|
default: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SwayScratchpad {
|
||||||
|
format: string;
|
||||||
|
"show-empty": boolean;
|
||||||
|
"format-icons": string[];
|
||||||
|
tooltip: boolean;
|
||||||
|
"tooltip-format": string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Temperature {
|
||||||
|
"critical-threshold": number;
|
||||||
|
format: string;
|
||||||
|
"format-icons": string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Tray {
|
||||||
|
spacing: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const parseConfig = async () => {
|
export const parseConfig = async () => {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import type { WaybarConfig } from "../configParser";
|
|||||||
import type { Module } from "../createModule";
|
import type { Module } from "../createModule";
|
||||||
import { createLabel } from "../Label";
|
import { createLabel } from "../Label";
|
||||||
|
|
||||||
|
type Status = "Unknown" | "Charging" | "Discharging" | "Plugged" | "Full";
|
||||||
|
|
||||||
export const createBatteryModule = (
|
export const createBatteryModule = (
|
||||||
module: Module,
|
module: Module,
|
||||||
config: WaybarConfig["battery"]
|
config: WaybarConfig["battery"]
|
||||||
@@ -15,8 +17,8 @@ export const createBatteryModule = (
|
|||||||
|
|
||||||
module.element.appendChild(label.element);
|
module.element.appendChild(label.element);
|
||||||
|
|
||||||
const getState = (battery: {level: number, charging: boolean}) => {
|
const getState = (battery: { level: number; charging: boolean }) => {
|
||||||
let status = "Unknown";
|
let status: Status = "Unknown";
|
||||||
|
|
||||||
if (battery.charging && battery.level < 1) {
|
if (battery.charging && battery.level < 1) {
|
||||||
status = "Charging";
|
status = "Charging";
|
||||||
@@ -26,34 +28,47 @@ export const createBatteryModule = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (battery.charging && battery.level === 1) {
|
if (battery.charging && battery.level === 1) {
|
||||||
status = "Plugged"
|
status = "Plugged";
|
||||||
}
|
}
|
||||||
if (!battery.charging && battery.level < 1) {
|
if (!battery.charging && battery.level < 1) {
|
||||||
status = "Discharging"
|
status = "Discharging";
|
||||||
}
|
}
|
||||||
return status;
|
return status;
|
||||||
}
|
};
|
||||||
|
|
||||||
let lastStatus = "Unknown"
|
let lastStatus = "Unknown";
|
||||||
const update = async () => {
|
const update = async () => {
|
||||||
// const battery = await navigator.getBattery();
|
// const battery = await navigator.getBattery();
|
||||||
|
|
||||||
const battery = {
|
const battery = {
|
||||||
level: Math.random(),
|
// level: Math.random(),
|
||||||
charging: Math.random() > 0.5,
|
// charging: Math.random() > 0.5,
|
||||||
|
level: 0.1,
|
||||||
|
charging: true,
|
||||||
};
|
};
|
||||||
const batteryPercent = Math.round(battery.level * 100);
|
const batteryPercent = Math.round(battery.level * 100);
|
||||||
|
|
||||||
|
|
||||||
const status = getState(battery);
|
const status = getState(battery);
|
||||||
|
|
||||||
|
|
||||||
module.element.classList.remove(lastStatus.toLowerCase());
|
module.element.classList.remove(lastStatus.toLowerCase());
|
||||||
module.element.classList.add(status.toLowerCase())
|
module.element.classList.add(status.toLowerCase());
|
||||||
lastStatus = status;
|
lastStatus = status;
|
||||||
module.element.title = status;
|
module.element.title = status;
|
||||||
|
|
||||||
const state = label.getState(batteryPercent, true);
|
const state = label.getState(batteryPercent, true);
|
||||||
console.log(state)
|
|
||||||
|
if (config) {
|
||||||
|
const _status = status.toLowerCase();
|
||||||
|
let format = "";
|
||||||
|
if (state && config["format-" + _status + "-" + state]) {
|
||||||
|
format = config["format-" + _status + "-" + state];
|
||||||
|
} else if (config["format-" + _status]) {
|
||||||
|
format = config["format-" + _status];
|
||||||
|
} else if (state && config["format-" + state]) {
|
||||||
|
format = config["format-" + state];
|
||||||
|
}
|
||||||
|
label.setFormat(format);
|
||||||
|
}
|
||||||
|
|
||||||
label.set({
|
label.set({
|
||||||
capacity: batteryPercent,
|
capacity: batteryPercent,
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
|
import type { WaybarConfig } from "../configParser";
|
||||||
import type { Module } from "../createModule";
|
import type { Module } from "../createModule";
|
||||||
|
|
||||||
export const createClockModule = (module: Module) => {
|
export const createClockModule = (
|
||||||
|
module: Module,
|
||||||
|
config: WaybarConfig["clock"]
|
||||||
|
) => {
|
||||||
const update = () => {
|
const update = () => {
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user