Files
custum-hyprpanel/customModules/utils.ts
Jas Singh 6cab0ff4b7 Added the hyprsunset module (requires hyprland v0.45.0) (#485)
* add sunset module

* Implement hyprsunset module.

* Update themes

* Undo vivids

* Fix vivids
2024-11-12 03:35:27 -08:00

239 lines
8.3 KiB
TypeScript

import { ResourceLabelType } from 'lib/types/bar';
import { GenericResourceData, Postfix } from 'lib/types/customModules/generic';
import { InputHandlerEvents, RunAsyncCommand } from 'lib/types/customModules/utils';
import { ThrottleFn, ThrottleFnCallback } from 'lib/types/utils';
import { Attribute, Child, EventArgs } from 'lib/types/widget';
import { Binding } from 'lib/utils';
import { openMenu } from 'modules/bar/utils';
import options from 'options';
import Gdk from 'types/@girs/gdk-3.0/gdk-3.0';
import { Variable as VariableType } from 'types/variable';
import Button from 'types/widgets/button';
const { scrollSpeed } = options.bar.customModules;
const handlePostInputUpdater = (postInputUpdater?: VariableType<boolean>): void => {
if (postInputUpdater !== undefined) {
postInputUpdater.value = !postInputUpdater.value;
}
};
export const runAsyncCommand: RunAsyncCommand = (cmd, events, fn, postInputUpdater?: VariableType<boolean>): void => {
if (cmd.startsWith('menu:')) {
const menuName = cmd.split(':')[1].trim().toLowerCase();
openMenu(events.clicked, events.event, `${menuName}menu`);
return;
}
Utils.execAsync(`bash -c "${cmd}"`)
.then((output) => {
handlePostInputUpdater(postInputUpdater);
if (fn !== undefined) {
fn(output);
}
})
.catch((err) => console.error(`Error running command "${cmd}": ${err})`));
};
export function throttleInput<T extends ThrottleFn>(func: T, limit: number): T {
let inThrottle: boolean;
return function (this: ThisParameterType<T>, ...args: Parameters<T>) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => {
inThrottle = false;
}, limit);
}
} as T;
}
export const throttledScrollHandler = (interval: number): ThrottleFn =>
throttleInput(
(cmd: string, events: EventArgs, fn: ThrottleFnCallback, postInputUpdater?: VariableType<boolean>) => {
runAsyncCommand(cmd, events, fn, postInputUpdater);
},
200 / interval,
);
const dummyVar = Variable('');
export const inputHandler = (
self: Button<Child, Attribute>,
{ onPrimaryClick, onSecondaryClick, onMiddleClick, onScrollUp, onScrollDown }: InputHandlerEvents,
postInputUpdater?: VariableType<boolean>,
): void => {
const sanitizeInput = (input: VariableType<string>): string => {
if (input === undefined) {
return '';
}
return input.value;
};
const updateHandlers = (): void => {
const interval = scrollSpeed.value;
const throttledHandler = throttledScrollHandler(interval);
self.on_primary_click = (clicked: Button<Child, Attribute>, event: Gdk.Event): void => {
runAsyncCommand(
sanitizeInput(onPrimaryClick?.cmd || dummyVar),
{ clicked, event },
onPrimaryClick.fn,
postInputUpdater,
);
};
self.on_secondary_click = (clicked: Button<Child, Attribute>, event: Gdk.Event): void => {
runAsyncCommand(
sanitizeInput(onSecondaryClick?.cmd || dummyVar),
{ clicked, event },
onSecondaryClick.fn,
postInputUpdater,
);
};
self.on_middle_click = (clicked: Button<Child, Attribute>, event: Gdk.Event): void => {
runAsyncCommand(
sanitizeInput(onMiddleClick?.cmd || dummyVar),
{ clicked, event },
onMiddleClick.fn,
postInputUpdater,
);
};
self.on_scroll_up = (clicked: Button<Child, Attribute>, event: Gdk.Event): void => {
throttledHandler(
sanitizeInput(onScrollUp?.cmd || dummyVar),
{ clicked, event },
onScrollUp.fn,
postInputUpdater,
);
};
self.on_scroll_down = (clicked: Button<Child, Attribute>, event: Gdk.Event): void => {
throttledHandler(
sanitizeInput(onScrollDown?.cmd || dummyVar),
{ clicked, event },
onScrollDown.fn,
postInputUpdater,
);
};
};
// Initial setup of event handlers
updateHandlers();
const sanitizeVariable = (someVar: VariableType<string> | undefined): Binding<string> => {
if (someVar === undefined || typeof someVar.bind !== 'function') {
return dummyVar.bind('value');
}
return someVar.bind('value');
};
// Re-run the update whenever scrollSpeed changes
Utils.merge(
[
scrollSpeed.bind('value'),
sanitizeVariable(onPrimaryClick),
sanitizeVariable(onSecondaryClick),
sanitizeVariable(onMiddleClick),
sanitizeVariable(onScrollUp),
sanitizeVariable(onScrollDown),
],
updateHandlers,
);
};
export const divide = ([total, used]: number[], round: boolean): number => {
const percentageTotal = (used / total) * 100;
if (round) {
return total > 0 ? Math.round(percentageTotal) : 0;
}
return total > 0 ? parseFloat(percentageTotal.toFixed(2)) : 0;
};
export const formatSizeInKiB = (sizeInBytes: number, round: boolean): number => {
const sizeInGiB = sizeInBytes / 1024 ** 1;
return round ? Math.round(sizeInGiB) : parseFloat(sizeInGiB.toFixed(2));
};
export const formatSizeInMiB = (sizeInBytes: number, round: boolean): number => {
const sizeInGiB = sizeInBytes / 1024 ** 2;
return round ? Math.round(sizeInGiB) : parseFloat(sizeInGiB.toFixed(2));
};
export const formatSizeInGiB = (sizeInBytes: number, round: boolean): number => {
const sizeInGiB = sizeInBytes / 1024 ** 3;
return round ? Math.round(sizeInGiB) : parseFloat(sizeInGiB.toFixed(2));
};
export const formatSizeInTiB = (sizeInBytes: number, round: boolean): number => {
const sizeInGiB = sizeInBytes / 1024 ** 4;
return round ? Math.round(sizeInGiB) : parseFloat(sizeInGiB.toFixed(2));
};
export const autoFormatSize = (sizeInBytes: number, round: boolean): number => {
// auto convert to GiB, MiB, KiB, TiB, or bytes
if (sizeInBytes >= 1024 ** 4) return formatSizeInTiB(sizeInBytes, round);
if (sizeInBytes >= 1024 ** 3) return formatSizeInGiB(sizeInBytes, round);
if (sizeInBytes >= 1024 ** 2) return formatSizeInMiB(sizeInBytes, round);
if (sizeInBytes >= 1024 ** 1) return formatSizeInKiB(sizeInBytes, round);
return sizeInBytes;
};
export const getPostfix = (sizeInBytes: number): Postfix => {
if (sizeInBytes >= 1024 ** 4) return 'TiB';
if (sizeInBytes >= 1024 ** 3) return 'GiB';
if (sizeInBytes >= 1024 ** 2) return 'MiB';
if (sizeInBytes >= 1024 ** 1) return 'KiB';
return 'B';
};
export const renderResourceLabel = (lblType: ResourceLabelType, rmUsg: GenericResourceData, round: boolean): string => {
const { used, total, percentage, free } = rmUsg;
const formatFunctions = {
TiB: formatSizeInTiB,
GiB: formatSizeInGiB,
MiB: formatSizeInMiB,
KiB: formatSizeInKiB,
B: (size: number): number => size,
};
// Get the data in proper GiB, MiB, KiB, TiB, or bytes
const totalSizeFormatted = autoFormatSize(total, round);
// get the postfix: one of [TiB, GiB, MiB, KiB, B]
const postfix = getPostfix(total);
// Determine which format function to use
const formatUsed = formatFunctions[postfix] || formatFunctions['B'];
const usedSizeFormatted = formatUsed(used, round);
if (lblType === 'used/total') {
return `${usedSizeFormatted}/${totalSizeFormatted} ${postfix}`;
}
if (lblType === 'used') {
return `${autoFormatSize(used, round)} ${getPostfix(used)}`;
}
if (lblType === 'free') {
return `${autoFormatSize(free, round)} ${getPostfix(free)}`;
}
return `${percentage}%`;
};
export const formatTooltip = (dataType: string, lblTyp: ResourceLabelType): string => {
switch (lblTyp) {
case 'used':
return `Used ${dataType}`;
case 'free':
return `Free ${dataType}`;
case 'used/total':
return `Used/Total ${dataType}`;
case 'percentage':
return `Percentage ${dataType} Usage`;
default:
return '';
}
};