Feat: Custom modules can now be created through a JSON file. (#887)
* Feat: Custom modules can now be created through a JSON file. * Added the ability to consume labels and icons. * Add all properties but styling. * Wrap up implementation. * Rename custom modules to basic modules to make way for new actually custom modules.
This commit is contained in:
10
.prettierrc
10
.prettierrc
@@ -4,5 +4,13 @@
|
||||
"trailingComma": "all",
|
||||
"printWidth": 120,
|
||||
"tabWidth": 4,
|
||||
"useTabs": false
|
||||
"useTabs": false,
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["**/*.jsonc"],
|
||||
"options": {
|
||||
"parser": "json"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
7
app.ts
7
app.ts
@@ -53,12 +53,15 @@ App.start({
|
||||
requestHandler(request: string, res: (response: unknown) => void) {
|
||||
runCLI(request, res);
|
||||
},
|
||||
main() {
|
||||
async main() {
|
||||
initializeStartupScripts();
|
||||
|
||||
Notifications();
|
||||
OSD();
|
||||
forMonitors(Bar).forEach((bar: JSX.Element) => bar);
|
||||
|
||||
const barsForMonitors = await forMonitors(Bar);
|
||||
barsForMonitors.forEach((bar: JSX.Element) => bar);
|
||||
|
||||
SettingsDialog();
|
||||
initializeMenus();
|
||||
|
||||
|
||||
7
package-lock.json
generated
7
package-lock.json
generated
@@ -24,13 +24,8 @@
|
||||
"typescript": "^5.6.2"
|
||||
}
|
||||
},
|
||||
"../../../../../usr/share/astal/gjs": {
|
||||
"name": "astal",
|
||||
"license": "LGPL-2.1"
|
||||
},
|
||||
"../../../../usr/share/astal/gjs": {
|
||||
"name": "astal",
|
||||
"extraneous": true,
|
||||
"license": "LGPL-2.1"
|
||||
},
|
||||
"node_modules/@eslint-community/eslint-utils": {
|
||||
@@ -606,7 +601,7 @@
|
||||
}
|
||||
},
|
||||
"node_modules/astal": {
|
||||
"resolved": "../../../../../usr/share/astal/gjs",
|
||||
"resolved": "../../../../usr/share/astal/gjs",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/available-typed-arrays": {
|
||||
|
||||
@@ -120,11 +120,11 @@ export const utilityCommands: Command[] = [
|
||||
|
||||
try {
|
||||
const oldFile = Gio.File.new_for_path(oldPath);
|
||||
const newFile = Gio.File.new_for_path(CONFIG);
|
||||
const newFile = Gio.File.new_for_path(CONFIG_FILE);
|
||||
|
||||
if (oldFile.query_exists(null)) {
|
||||
oldFile.move(newFile, Gio.FileCopyFlags.OVERWRITE, null, null);
|
||||
return `Configuration file moved to ${CONFIG}`;
|
||||
return `Configuration file moved to ${CONFIG_FILE}`;
|
||||
} else {
|
||||
return `Old configuration file does not exist at ${oldPath}`;
|
||||
}
|
||||
|
||||
64
src/components/bar/custom_modules/CustomModules.ts
Normal file
64
src/components/bar/custom_modules/CustomModules.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { Gio, readFileAsync } from 'astal';
|
||||
import { CustomBarModule } from './types';
|
||||
import { ModuleContainer } from './module_container';
|
||||
import { WidgetContainer } from '../shared/WidgetContainer';
|
||||
import { WidgetMap } from '..';
|
||||
|
||||
export class CustomModules {
|
||||
constructor() {}
|
||||
|
||||
public static async build(): Promise<WidgetMap> {
|
||||
const customModuleMap = await this._getCustomModules();
|
||||
const customModuleComponents: WidgetMap = {};
|
||||
|
||||
try {
|
||||
Object.entries(customModuleMap).map(([moduleName, moduleMetadata]) => {
|
||||
if (!moduleName.startsWith('custom/')) {
|
||||
return;
|
||||
}
|
||||
|
||||
customModuleComponents[moduleName] = (): JSX.Element =>
|
||||
WidgetContainer(ModuleContainer(moduleName, moduleMetadata));
|
||||
});
|
||||
|
||||
return customModuleComponents;
|
||||
} catch (error) {
|
||||
console.log(`Failed to build custom modules in ${CONFIG_DIR}: ${error}`);
|
||||
throw new Error(`Failed to build custom modules in ${CONFIG_DIR}: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
private static async _getCustomModules(): Promise<Record<string, CustomBarModule>> {
|
||||
try {
|
||||
const filesInConfigDir = await this._getFilesInConfigDir();
|
||||
const modulesFile = filesInConfigDir.find((file) => file.match(/^modules(\.json)?$/));
|
||||
const pathToModulesFile = `${CONFIG_DIR}/${modulesFile}`;
|
||||
|
||||
const customModulesFileContent = await readFileAsync(pathToModulesFile);
|
||||
|
||||
const modulesObject = JSON.parse(customModulesFileContent);
|
||||
|
||||
return modulesObject;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to parse modules file in ${CONFIG_DIR}: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
private static async _getFilesInConfigDir(): Promise<string[]> {
|
||||
const file = Gio.File.new_for_path(CONFIG_DIR);
|
||||
const enumerator = file.enumerate_children('standard::*', Gio.FileQueryInfoFlags.NONE, null);
|
||||
const fileNames = [];
|
||||
|
||||
for (const info of enumerator) {
|
||||
const fileType = info.get_file_type();
|
||||
const fileName = info.get_name();
|
||||
|
||||
if (fileType === Gio.FileType.REGULAR) {
|
||||
fileNames.push(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
enumerator.close(null);
|
||||
return fileNames;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
import { isPrimitive } from 'src/lib/utils';
|
||||
import { CustomBarModuleIcon } from '../../types';
|
||||
import { parseCommandOutputJson } from './utils';
|
||||
|
||||
const ERROR_ICON = '';
|
||||
|
||||
/**
|
||||
* Resolves the appropriate icon for a custom bar module based on its configuration and command output
|
||||
*
|
||||
* @param moduleName - The name of the module requesting the icon
|
||||
* @param commandOutput - The raw output string from the module's command execution
|
||||
* @param moduleIcon - The module's configuration metadata containing icon settings
|
||||
* @returns The resolved icon string based on the configuration, or ERROR_ICON if resolution fails
|
||||
*
|
||||
* @example
|
||||
* // Using a static icon
|
||||
* getIcon('myModule', '', { icon: '🚀' }) // returns '🚀'
|
||||
*
|
||||
* // Using an array of icons based on percentage
|
||||
* getIcon('myModule', '{"percentage": 50}', { icon: ['😡', '😐', '😊'] })
|
||||
*
|
||||
* // Using an object mapping for specific states
|
||||
* getIcon('myModule', '{"alt": "success"}', { icon: { success: '✅', error: '❌' } })
|
||||
*/
|
||||
export function getIcon(moduleName: string, commandOutput: string, moduleIcon: CustomBarModuleIcon): string {
|
||||
if (Array.isArray(moduleIcon)) {
|
||||
return getIconFromArray(moduleName, commandOutput, moduleIcon);
|
||||
}
|
||||
|
||||
if (typeof moduleIcon === 'object') {
|
||||
return getIconFromObject(moduleName, commandOutput, moduleIcon);
|
||||
}
|
||||
|
||||
return moduleIcon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves an icon from an object configuration based on the 'alt' value in command output
|
||||
*
|
||||
* @param moduleName - The name of the module requesting the icon
|
||||
* @param commandOutput - The raw output string from the module's command execution
|
||||
* @param iconObject - Object mapping alternate text to corresponding icons
|
||||
* @returns The matched icon string or ERROR_ICON if resolution fails
|
||||
*
|
||||
* @throws Logs error and returns ERROR_ICON if:
|
||||
* - Command output cannot be parsed
|
||||
* - 'alt' value is not a string
|
||||
* - No matching icon is found for the alt text
|
||||
* - Corresponding icon value is not a string
|
||||
*/
|
||||
function getIconFromObject(moduleName: string, commandOutput: string, iconObject: Record<string, unknown>): string {
|
||||
try {
|
||||
const commandResults: CommandResults = parseCommandOutputJson(moduleName, commandOutput);
|
||||
|
||||
if (!isPrimitive(commandResults?.alt) || commandResults?.alt === undefined) {
|
||||
console.error(`Expected 'alt' to be a primitive for module: ${moduleName}`);
|
||||
return ERROR_ICON;
|
||||
}
|
||||
|
||||
const resultsAltText = String(commandResults?.alt);
|
||||
|
||||
const correspondingAltIcon = iconObject[resultsAltText];
|
||||
|
||||
if (correspondingAltIcon === undefined) {
|
||||
console.error(`Corresponding icon ${resultsAltText} not found for module: ${moduleName}`);
|
||||
return typeof iconObject.default === 'string' ? iconObject.default : ERROR_ICON;
|
||||
}
|
||||
|
||||
if (typeof correspondingAltIcon !== 'string') {
|
||||
console.error(`Corresponding icon ${resultsAltText} is not a string for module: ${moduleName}`);
|
||||
return ERROR_ICON;
|
||||
}
|
||||
|
||||
return correspondingAltIcon;
|
||||
} catch {
|
||||
return ERROR_ICON;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves an icon from an array configuration based on the percentage value in command output
|
||||
*
|
||||
* @param moduleName - The name of the module requesting the icon
|
||||
* @param commandOutput - The raw output string from the module's command execution
|
||||
* @param iconArray - Array of icons to select from based on percentage ranges
|
||||
* @returns The appropriate icon string based on the percentage or ERROR_ICON if resolution fails
|
||||
*
|
||||
* @example
|
||||
* // With iconArray ['😡', '😐', '😊']
|
||||
* // 0-33%: returns '😡'
|
||||
* // 34-66%: returns '😐'
|
||||
* // 67-100%: returns '😊'
|
||||
*
|
||||
* @throws Logs error and returns ERROR_ICON if:
|
||||
* - Command output cannot be parsed
|
||||
* - Percentage value is not a number
|
||||
* - Percentage is NaN or exceeds 100
|
||||
*/
|
||||
function getIconFromArray(moduleName: string, commandOutput: string, iconArray: string[]): string {
|
||||
try {
|
||||
const commandResults: CommandResults = parseCommandOutputJson(moduleName, commandOutput);
|
||||
const resultsPercentage = commandResults?.percentage;
|
||||
|
||||
if (typeof resultsPercentage !== 'number') {
|
||||
console.error(`Expected percentage to be a number for module: ${moduleName}`);
|
||||
return ERROR_ICON;
|
||||
}
|
||||
|
||||
if (isNaN(resultsPercentage) || resultsPercentage > 100) {
|
||||
console.error(`Expected percentage to be between 1-100 for module: ${moduleName}`);
|
||||
return ERROR_ICON;
|
||||
}
|
||||
|
||||
const step = 100 / iconArray.length;
|
||||
|
||||
const iconForStep = iconArray.find((_, index) => resultsPercentage <= step * (index + 1));
|
||||
|
||||
return iconForStep || ERROR_ICON;
|
||||
} catch {
|
||||
return ERROR_ICON;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the expected structure of parsed command output
|
||||
*/
|
||||
type CommandResults = {
|
||||
/** Alternate text identifier for object-based icon configuration */
|
||||
alt?: string;
|
||||
/** Percentage value for array-based icon configuration (0-100) */
|
||||
percentage?: number;
|
||||
};
|
||||
@@ -0,0 +1,113 @@
|
||||
import { isPrimitive } from 'src/lib/utils';
|
||||
|
||||
/**
|
||||
* Generates a label based on module command output and a template configuration.
|
||||
*
|
||||
* @param moduleName - The name of the module (used for error reporting)
|
||||
* @param commandOutput - The raw output from a module command, expected to be a JSON string or plain text
|
||||
* @param labelConfig - A template string containing variables in the format {path.to.value}
|
||||
* @returns A formatted label with template variables replaced with actual values
|
||||
*
|
||||
* @example
|
||||
* // For a JSON command output: {"user": {"name": "Jim Halpert"}}
|
||||
* // And labelConfig: "Hello, {user.name}!"
|
||||
* // Returns: "Hello, Jim Halpert!"
|
||||
*/
|
||||
export function getLabel(moduleName: string, commandOutput: string, labelConfig: string): string {
|
||||
const processedCommandOutput = tryParseJson(moduleName, commandOutput);
|
||||
const regexForTemplateVariables = /\{([^{}]*)\}/g;
|
||||
|
||||
return labelConfig.replace(regexForTemplateVariables, (_, path) => {
|
||||
return getValueForTemplateVariable(path, processedCommandOutput);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a value from command output based on a template variable path.
|
||||
*
|
||||
* @param templatePath - The dot-notation path to extract (e.g., "user.name")
|
||||
* @param commandOutput - The processed command output (either a string or object)
|
||||
* @returns The extracted value as a string, or empty string if not found
|
||||
*/
|
||||
function getValueForTemplateVariable(templatePath: string, commandOutput: string | Record<string, unknown>): string {
|
||||
if (typeof commandOutput === 'string') {
|
||||
return getTemplateValueForStringOutput(templatePath, commandOutput);
|
||||
}
|
||||
|
||||
if (typeof commandOutput === 'object' && commandOutput !== null) {
|
||||
return getTemplateValueForObjectOutput(templatePath, commandOutput);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a template value from string command output.
|
||||
*
|
||||
* @param templatePath - The path to extract value from
|
||||
* @param commandOutput - The string command output
|
||||
* @returns The entire string if path is empty, otherwise empty string
|
||||
*/
|
||||
function getTemplateValueForStringOutput(templatePath: string, commandOutput: string): string {
|
||||
if (templatePath === '') {
|
||||
return commandOutput;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a template value from object command output using dot notation.
|
||||
*
|
||||
* @param templatePath - The dot-notation path to extract (e.g., "user.name")
|
||||
* @param commandOutput - The object representing parsed command output
|
||||
* @returns The extracted value as a string, or empty string if path is invalid or value is not primitive
|
||||
*/
|
||||
function getTemplateValueForObjectOutput(templatePath: string, commandOutput: Record<string, unknown>): string {
|
||||
const pathParts = templatePath.split('.');
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return value !== null && !Array.isArray(value) && typeof value === 'object';
|
||||
}
|
||||
|
||||
try {
|
||||
const result = pathParts.reduce<unknown>((acc, part) => {
|
||||
if (!isRecord(acc)) {
|
||||
throw new Error('Path unreachable');
|
||||
}
|
||||
|
||||
return acc[part];
|
||||
}, commandOutput);
|
||||
|
||||
return isPrimitive(result) && result !== undefined ? String(result) : '';
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to parse a JSON string, with fallback to the original string.
|
||||
*
|
||||
* @param moduleName - The name of the module (used for error reporting)
|
||||
* @param commandOutput - The raw string output to parse as JSON
|
||||
* @returns A parsed object if valid JSON and an object, otherwise the original string
|
||||
*/
|
||||
function tryParseJson(moduleName: string, commandOutput: string): string | Record<string, unknown> {
|
||||
try {
|
||||
if (typeof commandOutput !== 'string') {
|
||||
console.error(
|
||||
`Expected command output to be a string but found ${typeof commandOutput} for module: ${moduleName}`,
|
||||
);
|
||||
return '';
|
||||
}
|
||||
|
||||
const parsedCommand = JSON.parse(commandOutput);
|
||||
|
||||
if (typeof parsedCommand === 'object' && parsedCommand !== null && !Array.isArray(parsedCommand)) {
|
||||
return parsedCommand as Record<string, unknown>;
|
||||
}
|
||||
|
||||
return commandOutput;
|
||||
} catch {
|
||||
return commandOutput;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
export function parseCommandOutputJson(moduleName: string, cmdOutput: unknown): Record<string, unknown> {
|
||||
try {
|
||||
if (typeof cmdOutput !== 'string') {
|
||||
throw new Error('Input must be a string');
|
||||
}
|
||||
|
||||
return JSON.parse(cmdOutput);
|
||||
} catch {
|
||||
throw new Error(`The command output for the following module is not valid JSON: ${moduleName}`);
|
||||
}
|
||||
}
|
||||
48
src/components/bar/custom_modules/module_container/index.tsx
Normal file
48
src/components/bar/custom_modules/module_container/index.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import { BarBoxChild } from 'src/lib/types/bar.js';
|
||||
import { CustomBarModule } from '../types';
|
||||
import { Module } from '../../shared/Module';
|
||||
import { Astal } from 'astal/gtk3';
|
||||
import { bind, Variable } from 'astal';
|
||||
import { getIcon } from './helpers/icon';
|
||||
import { getLabel } from './helpers/label';
|
||||
import { initActionListener, initCommandPoller, setupModuleInteractions } from './setup';
|
||||
|
||||
export const ModuleContainer = (moduleName: string, moduleMetadata: CustomBarModule): BarBoxChild => {
|
||||
const {
|
||||
icon: moduleIcon = '',
|
||||
label: moduleLabel = '',
|
||||
tooltip: moduleTooltip = '',
|
||||
truncationSize: moduleTruncation = -1,
|
||||
execute: moduleExecute = '',
|
||||
executeOnAction: moduleExecuteOnAction = '',
|
||||
interval: moduleInterval = -1,
|
||||
hideOnEmpty: moduleHideOnEmpty = false,
|
||||
scrollThreshold: moduleScrollThreshold = 4,
|
||||
actions: moduleActions = {},
|
||||
} = moduleMetadata;
|
||||
|
||||
const pollingInterval: Variable<number> = Variable(moduleInterval);
|
||||
const actionExecutionListener: Variable<boolean> = Variable(true);
|
||||
const commandOutput: Variable<string> = Variable('');
|
||||
|
||||
const commandPoller = initCommandPoller(commandOutput, pollingInterval, moduleExecute, moduleInterval);
|
||||
initActionListener(actionExecutionListener, moduleExecuteOnAction, commandOutput);
|
||||
|
||||
const module = Module({
|
||||
textIcon: bind(commandOutput).as((cmdOutput) => getIcon(moduleName, cmdOutput, moduleIcon)),
|
||||
tooltipText: bind(commandOutput).as((cmdOutput) => getLabel(moduleName, cmdOutput, moduleTooltip)),
|
||||
boxClass: `cmodule-${moduleName.replace(/custom\//, '')}`,
|
||||
label: bind(commandOutput).as((cmdOutput) => getLabel(moduleName, cmdOutput, moduleLabel)),
|
||||
truncationSize: bind(Variable(typeof moduleTruncation === 'number' ? moduleTruncation : -1)),
|
||||
props: {
|
||||
setup: (self: Astal.Button) =>
|
||||
setupModuleInteractions(self, moduleActions, actionExecutionListener, moduleScrollThreshold),
|
||||
onDestroy: () => {
|
||||
commandPoller.stop();
|
||||
},
|
||||
},
|
||||
isVis: bind(commandOutput).as((cmdOutput) => (moduleHideOnEmpty ? cmdOutput.length > 0 : true)),
|
||||
});
|
||||
|
||||
return module;
|
||||
};
|
||||
76
src/components/bar/custom_modules/module_container/setup.ts
Normal file
76
src/components/bar/custom_modules/module_container/setup.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { Variable, bind, execAsync } from 'astal';
|
||||
import { Astal } from 'astal/gtk3';
|
||||
import { BashPoller } from 'src/lib/poller/BashPoller';
|
||||
import { CustomBarModule } from '../types';
|
||||
import { inputHandler } from '../../utils/helpers';
|
||||
|
||||
export function initCommandPoller(
|
||||
commandOutput: Variable<string>,
|
||||
pollingInterval: Variable<number>,
|
||||
moduleExecute: string,
|
||||
moduleInterval: number,
|
||||
): BashPoller<string, []> {
|
||||
const commandPoller = new BashPoller<string, []>(
|
||||
commandOutput,
|
||||
[],
|
||||
bind(pollingInterval),
|
||||
moduleExecute || '',
|
||||
(commandResult: string) => commandResult,
|
||||
);
|
||||
|
||||
if (moduleInterval >= 0) {
|
||||
commandPoller.initialize();
|
||||
}
|
||||
|
||||
return commandPoller;
|
||||
}
|
||||
|
||||
export function initActionListener(
|
||||
actionExecutionListener: Variable<boolean>,
|
||||
moduleExecuteOnAction: string,
|
||||
commandOutput: Variable<string>,
|
||||
): void {
|
||||
actionExecutionListener.subscribe(() => {
|
||||
if (typeof moduleExecuteOnAction !== 'string' || !moduleExecuteOnAction.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
execAsync(moduleExecuteOnAction).then((cmdOutput) => {
|
||||
commandOutput.set(cmdOutput);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up user interaction handlers for the module
|
||||
*/
|
||||
export function setupModuleInteractions(
|
||||
element: Astal.Button,
|
||||
moduleActions: CustomBarModule['actions'],
|
||||
actionListener: Variable<boolean>,
|
||||
moduleScrollThreshold: number,
|
||||
): void {
|
||||
const scrollThreshold = moduleScrollThreshold >= 0 ? moduleScrollThreshold : 1;
|
||||
inputHandler(
|
||||
element,
|
||||
{
|
||||
onPrimaryClick: {
|
||||
cmd: Variable(moduleActions?.onLeftClick ?? ''),
|
||||
},
|
||||
onSecondaryClick: {
|
||||
cmd: Variable(moduleActions?.onRightClick ?? ''),
|
||||
},
|
||||
onMiddleClick: {
|
||||
cmd: Variable(moduleActions?.onMiddleClick ?? ''),
|
||||
},
|
||||
onScrollUp: {
|
||||
cmd: Variable(moduleActions?.onScrollUp ?? ''),
|
||||
},
|
||||
onScrollDown: {
|
||||
cmd: Variable(moduleActions?.onScrollDown ?? ''),
|
||||
},
|
||||
},
|
||||
actionListener,
|
||||
scrollThreshold,
|
||||
);
|
||||
}
|
||||
20
src/components/bar/custom_modules/types.ts
Normal file
20
src/components/bar/custom_modules/types.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
export type CustomBarModuleActions = {
|
||||
onLeftClick?: string;
|
||||
onRightClick?: string;
|
||||
onMiddleClick?: string;
|
||||
onScrollUp?: string;
|
||||
onScrollDown?: string;
|
||||
};
|
||||
export type CustomBarModule = {
|
||||
icon?: CustomBarModuleIcon;
|
||||
label?: string;
|
||||
tooltip?: string;
|
||||
truncationSize?: number;
|
||||
execute?: string;
|
||||
executeOnAction?: string;
|
||||
interval?: number;
|
||||
hideOnEmpty?: boolean;
|
||||
scrollThreshold?: number;
|
||||
actions?: CustomBarModuleActions;
|
||||
};
|
||||
export type CustomBarModuleIcon = string | string[] | Record<string, string>;
|
||||
@@ -10,7 +10,7 @@ import { BatteryLabel } from '../../components/bar/modules/battery/index';
|
||||
import { Clock } from '../../components/bar/modules/clock/index';
|
||||
import { SysTray } from '../../components/bar/modules/systray/index';
|
||||
|
||||
// Custom Modules
|
||||
// Basic Modules
|
||||
import { Microphone } from '../../components/bar/modules/microphone/index';
|
||||
import { Ram } from '../../components/bar/modules/ram/index';
|
||||
import { Cpu } from '../../components/bar/modules/cpu/index';
|
||||
@@ -40,7 +40,7 @@ export {
|
||||
Clock,
|
||||
SysTray,
|
||||
|
||||
// Custom Modules
|
||||
// Basic Modules
|
||||
Microphone,
|
||||
Ram,
|
||||
Cpu,
|
||||
|
||||
@@ -10,8 +10,6 @@ import {
|
||||
BatteryLabel,
|
||||
Clock,
|
||||
SysTray,
|
||||
|
||||
// Custom Modules
|
||||
Microphone,
|
||||
Ram,
|
||||
Cpu,
|
||||
@@ -37,12 +35,13 @@ import Astal from 'gi://Astal?version=3.0';
|
||||
import { bind, Variable } from 'astal';
|
||||
import { getLayoutForMonitor, isLayoutEmpty } from './utils/monitors';
|
||||
import { GdkMonitorMapper } from './utils/GdkMonitorMapper';
|
||||
import { CustomModules } from './custom_modules/CustomModules';
|
||||
|
||||
const { layouts } = options.bar;
|
||||
const { location } = options.theme.bar;
|
||||
const { location: borderLocation } = options.theme.bar.border;
|
||||
|
||||
const widget = {
|
||||
let widgets: WidgetMap = {
|
||||
battery: (): JSX.Element => WidgetContainer(BatteryLabel()),
|
||||
dashboard: (): JSX.Element => WidgetContainer(Menu()),
|
||||
workspaces: (monitor: number): JSX.Element => WidgetContainer(Workspaces(monitor)),
|
||||
@@ -73,7 +72,16 @@ const widget = {
|
||||
|
||||
const gdkMonitorMapper = new GdkMonitorMapper();
|
||||
|
||||
export const Bar = (monitor: number): JSX.Element => {
|
||||
export const Bar = async (monitor: number): Promise<JSX.Element> => {
|
||||
try {
|
||||
const customWidgets = await CustomModules.build();
|
||||
widgets = {
|
||||
...widgets,
|
||||
...customWidgets,
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
const hyprlandMonitor = gdkMonitorMapper.mapGdkToHyprland(monitor);
|
||||
|
||||
const computeVisibility = bind(layouts).as(() => {
|
||||
@@ -116,22 +124,22 @@ export const Bar = (monitor: number): JSX.Element => {
|
||||
const foundLayout = getLayoutForMonitor(hyprlandMonitor, currentLayouts);
|
||||
|
||||
return foundLayout.left
|
||||
.filter((mod) => Object.keys(widget).includes(mod))
|
||||
.map((w) => widget[w](hyprlandMonitor));
|
||||
.filter((mod) => Object.keys(widgets).includes(mod))
|
||||
.map((w) => widgets[w](hyprlandMonitor));
|
||||
});
|
||||
const middleBinding = Variable.derive([bind(layouts)], (currentLayouts) => {
|
||||
const foundLayout = getLayoutForMonitor(hyprlandMonitor, currentLayouts);
|
||||
|
||||
return foundLayout.middle
|
||||
.filter((mod) => Object.keys(widget).includes(mod))
|
||||
.map((w) => widget[w](hyprlandMonitor));
|
||||
.filter((mod) => Object.keys(widgets).includes(mod))
|
||||
.map((w) => widgets[w](hyprlandMonitor));
|
||||
});
|
||||
const rightBinding = Variable.derive([bind(layouts)], (currentLayouts) => {
|
||||
const foundLayout = getLayoutForMonitor(hyprlandMonitor, currentLayouts);
|
||||
|
||||
return foundLayout.right
|
||||
.filter((mod) => Object.keys(widget).includes(mod))
|
||||
.map((w) => widget[w](hyprlandMonitor));
|
||||
.filter((mod) => Object.keys(widgets).includes(mod))
|
||||
.map((w) => widgets[w](hyprlandMonitor));
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -178,3 +186,7 @@ export const Bar = (monitor: number): JSX.Element => {
|
||||
</window>
|
||||
);
|
||||
};
|
||||
|
||||
export type WidgetMap = {
|
||||
[K in string]: (monitor: number) => JSX.Element;
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Gtk } from 'astal/gtk3';
|
||||
export const CustomModuleSettings = (): JSX.Element => {
|
||||
return (
|
||||
<scrollable
|
||||
name={'Custom Modules'}
|
||||
name={'Basic Modules'}
|
||||
className="menu-theme-page customModules paged-container"
|
||||
vscroll={Gtk.PolicyType.AUTOMATIC}
|
||||
hscroll={Gtk.PolicyType.AUTOMATIC}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Gtk } from 'astal/gtk3';
|
||||
export const CustomModuleTheme = (): JSX.Element => {
|
||||
return (
|
||||
<scrollable
|
||||
name={'Custom Modules'}
|
||||
name={'Basic Modules'}
|
||||
className="menu-theme-page customModules paged-container"
|
||||
vscroll={Gtk.PolicyType.AUTOMATIC}
|
||||
hscroll={Gtk.PolicyType.AUTOMATIC}
|
||||
|
||||
@@ -10,6 +10,7 @@ export const Module = ({
|
||||
textIcon,
|
||||
useTextIcon = bind(Variable(false)),
|
||||
label,
|
||||
truncationSize = bind(Variable(-1)),
|
||||
tooltipText = '',
|
||||
boxClass,
|
||||
isVis,
|
||||
@@ -21,15 +22,17 @@ export const Module = ({
|
||||
hook,
|
||||
}: BarModule): BarBoxChild => {
|
||||
const getIconWidget = (useTxtIcn: boolean): JSX.Element | undefined => {
|
||||
let iconWidget: JSX.Element | undefined;
|
||||
const className = `txt-icon bar-button-icon module-icon ${boxClass}`;
|
||||
|
||||
if (icon !== undefined && icon.get() != '' && !useTxtIcn) {
|
||||
iconWidget = <icon className={`txt-icon bar-button-icon module-icon ${boxClass}`} icon={icon} />;
|
||||
} else if (textIcon !== undefined && textIcon.get() != '') {
|
||||
iconWidget = <label className={`txt-icon bar-button-icon module-icon ${boxClass}`} label={textIcon} />;
|
||||
const icn = typeof icon === 'string' ? icon : icon?.get();
|
||||
if (!useTxtIcn && icn?.length) {
|
||||
return <icon className={className} icon={icon} />;
|
||||
}
|
||||
|
||||
return iconWidget;
|
||||
const textIcn = typeof textIcon === 'string' ? textIcon : textIcon?.get();
|
||||
if (textIcn?.length) {
|
||||
return <label className={className} label={textIcon} />;
|
||||
}
|
||||
};
|
||||
|
||||
const componentClass = Variable.derive(
|
||||
@@ -60,6 +63,8 @@ export const Module = ({
|
||||
childrenArray.push(
|
||||
<label
|
||||
className={`bar-button-label module-label ${boxClass}`}
|
||||
truncate={truncationSize.as((truncSize) => truncSize > 0)}
|
||||
maxWidthChars={truncationSize.as((truncSize) => truncSize)}
|
||||
label={label ?? ''}
|
||||
setup={labelHook}
|
||||
/>,
|
||||
|
||||
@@ -42,7 +42,7 @@ export const runAsyncCommand: RunAsyncCommand = (cmd, events, fn, postInputUpdat
|
||||
if (cmd.startsWith('menu:')) {
|
||||
const menuName = cmd.split(':')[1].trim().toLowerCase();
|
||||
openMenu(events.clicked, events.event, `${menuName}menu`);
|
||||
|
||||
handlePostInputUpdater(postInputUpdater);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -122,8 +122,9 @@ export const inputHandler = (
|
||||
onScrollDown: onScrollDownInput,
|
||||
}: InputHandlerEvents,
|
||||
postInputUpdater?: Variable<boolean>,
|
||||
customScrollThreshold?: number,
|
||||
): void => {
|
||||
const sanitizeInput = (input?: Variable<string> | Variable<string>): string => {
|
||||
const sanitizeInput = (input?: Variable<string>): string => {
|
||||
if (input === undefined) {
|
||||
return '';
|
||||
}
|
||||
@@ -131,7 +132,7 @@ export const inputHandler = (
|
||||
};
|
||||
|
||||
const updateHandlers = (): UpdateHandlers => {
|
||||
const interval = scrollSpeed.get();
|
||||
const interval = customScrollThreshold ?? scrollSpeed.get();
|
||||
const throttledHandler = throttledScrollHandler(interval);
|
||||
|
||||
const disconnectPrimaryClick = onPrimaryClick(self, (clicked: GtkWidget, event: Gdk.Event) => {
|
||||
|
||||
@@ -16,7 +16,7 @@ export const themePages = [
|
||||
'System Tray',
|
||||
'Volume Menu',
|
||||
'Power Menu',
|
||||
'Custom Modules',
|
||||
'Basic Modules',
|
||||
] as const;
|
||||
|
||||
export const configPages = [
|
||||
@@ -28,7 +28,7 @@ export const configPages = [
|
||||
'Volume',
|
||||
'Clock Menu',
|
||||
'Dashboard Menu',
|
||||
'Custom Modules',
|
||||
'Basic Modules',
|
||||
'Power Menu',
|
||||
] as const;
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ export const BarGeneral = (): JSX.Element => {
|
||||
title="Config"
|
||||
subtitle="WARNING: Importing a configuration will replace your current configuration settings."
|
||||
type="config_import"
|
||||
exportData={{ filePath: CONFIG, themeOnly: false }}
|
||||
exportData={{ filePath: CONFIG_FILE, themeOnly: false }}
|
||||
/>
|
||||
<Option
|
||||
opt={options.hyprpanel.restartAgs}
|
||||
|
||||
@@ -22,7 +22,7 @@ export const MenuTheme = (): JSX.Element => {
|
||||
title="Theme"
|
||||
subtitle="WARNING: Importing a theme will replace your current theme color settings."
|
||||
type="config_import"
|
||||
exportData={{ filePath: CONFIG, themeOnly: true }}
|
||||
exportData={{ filePath: CONFIG_FILE, themeOnly: true }}
|
||||
/>
|
||||
<Option
|
||||
opt={options.theme.bar.menus.monochrome}
|
||||
|
||||
@@ -272,7 +272,7 @@ export const importFiles = (themeOnly: boolean = false): void => {
|
||||
iconName: icons.ui.info,
|
||||
});
|
||||
|
||||
const optionsConfigFile = Gio.File.new_for_path(CONFIG);
|
||||
const optionsConfigFile = Gio.File.new_for_path(CONFIG_FILE);
|
||||
|
||||
const [optionsSuccess, optionsContent] = optionsConfigFile.load_contents(null);
|
||||
|
||||
@@ -289,7 +289,7 @@ export const importFiles = (themeOnly: boolean = false): void => {
|
||||
: filterConfigForNonTheme(importedConfig);
|
||||
optionsConfig = { ...optionsConfig, ...filteredConfig };
|
||||
|
||||
saveConfigToFile(optionsConfig, CONFIG);
|
||||
saveConfigToFile(optionsConfig, CONFIG_FILE);
|
||||
}
|
||||
dialog.destroy();
|
||||
bash(restartCommand.get());
|
||||
|
||||
@@ -14,7 +14,7 @@ globalThis.useTheme = (filePath: string): void => {
|
||||
return;
|
||||
}
|
||||
|
||||
const optionsConfigFile = Gio.File.new_for_path(CONFIG);
|
||||
const optionsConfigFile = Gio.File.new_for_path(CONFIG_FILE);
|
||||
|
||||
const [optionsSuccess, optionsContent] = optionsConfigFile.load_contents(null);
|
||||
|
||||
@@ -27,7 +27,7 @@ globalThis.useTheme = (filePath: string): void => {
|
||||
const filteredConfig = filterConfigForThemeOnly(importedConfig);
|
||||
optionsConfig = { ...optionsConfig, ...filteredConfig };
|
||||
|
||||
saveConfigToFile(optionsConfig, CONFIG);
|
||||
saveConfigToFile(optionsConfig, CONFIG_FILE);
|
||||
bash(restartCommand.get());
|
||||
} catch (error) {
|
||||
errorHandler(error);
|
||||
|
||||
@@ -100,7 +100,7 @@ export class Opt<T = unknown> extends Variable<T> {
|
||||
super.set(value);
|
||||
|
||||
if (writeDisk) {
|
||||
const raw = readFile(CONFIG);
|
||||
const raw = readFile(CONFIG_FILE);
|
||||
let config: Record<string, unknown> = {};
|
||||
if (raw && raw.trim() !== '') {
|
||||
try {
|
||||
@@ -119,7 +119,7 @@ export class Opt<T = unknown> extends Variable<T> {
|
||||
}
|
||||
}
|
||||
config[this._id] = value;
|
||||
writeFile(CONFIG, JSON.stringify(config, null, 2));
|
||||
writeFile(CONFIG_FILE, JSON.stringify(config, null, 2));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -225,9 +225,9 @@ function getOptions(optionsObj: Record<string, unknown>, path = '', arr: Opt[] =
|
||||
* @returns The original object extended with additional methods for handling options.
|
||||
*/
|
||||
export function mkOptions<T extends object>(optionsObj: T): T & MkOptionsResult {
|
||||
ensureDirectory(CONFIG.split('/').slice(0, -1).join('/'));
|
||||
ensureDirectory(CONFIG_FILE.split('/').slice(0, -1).join('/'));
|
||||
|
||||
const rawConfig = readFile(CONFIG);
|
||||
const rawConfig = readFile(CONFIG_FILE);
|
||||
|
||||
let config: Record<string, unknown> = {};
|
||||
if (rawConfig && rawConfig.trim() !== '') {
|
||||
@@ -254,7 +254,7 @@ export function mkOptions<T extends object>(optionsObj: T): T & MkOptionsResult
|
||||
// the config menu
|
||||
const debounceTimeMs = 200;
|
||||
let lastEventTime = Date.now();
|
||||
monitorFile(CONFIG, () => {
|
||||
monitorFile(CONFIG_FILE, () => {
|
||||
if (Date.now() - lastEventTime < debounceTimeMs) {
|
||||
return;
|
||||
}
|
||||
@@ -262,7 +262,7 @@ export function mkOptions<T extends object>(optionsObj: T): T & MkOptionsResult
|
||||
|
||||
let newConfig: Record<string, unknown> = {};
|
||||
|
||||
const rawConfig = readFile(CONFIG);
|
||||
const rawConfig = readFile(CONFIG_FILE);
|
||||
if (rawConfig && rawConfig.trim() !== '') {
|
||||
try {
|
||||
newConfig = JSON.parse(rawConfig) as Record<string, unknown>;
|
||||
|
||||
@@ -9,30 +9,30 @@ const { layouts } = options.bar;
|
||||
* A class that manages the polling lifecycle, including interval management and execution state.
|
||||
*/
|
||||
export class Poller {
|
||||
private intervalInstance: AstalIO.Time | null = null;
|
||||
private isExecuting: boolean = false;
|
||||
private pollingFunction: () => Promise<void>;
|
||||
private _intervalInstance: AstalIO.Time | null = null;
|
||||
private _isExecuting: boolean = false;
|
||||
private _pollingFunction: () => Promise<void>;
|
||||
|
||||
/**
|
||||
* Creates an instance of Poller.
|
||||
* @param pollingInterval - The interval at which polling occurs.
|
||||
* @param trackers - An array of trackers to monitor.
|
||||
* @param _pollingInterval - The interval at which polling occurs.
|
||||
* @param _trackers - An array of trackers to monitor.
|
||||
* @param pollingFunction - The function to execute during each poll.
|
||||
*/
|
||||
constructor(
|
||||
private pollingInterval: Bind,
|
||||
private trackers: Bind[],
|
||||
private _pollingInterval: Bind,
|
||||
private _trackers: Bind[],
|
||||
pollingFunction: () => Promise<void>,
|
||||
) {
|
||||
this.pollingFunction = pollingFunction;
|
||||
this._pollingFunction = pollingFunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the polling process by setting up the interval.
|
||||
*/
|
||||
public start(): void {
|
||||
Variable.derive([this.pollingInterval, ...this.trackers], (intervalMs: number) => {
|
||||
this.executePolling(intervalMs);
|
||||
Variable.derive([this._pollingInterval, ...this._trackers], (intervalMs: number) => {
|
||||
this._executePolling(intervalMs);
|
||||
})();
|
||||
}
|
||||
|
||||
@@ -40,9 +40,9 @@ export class Poller {
|
||||
* Stops the polling process and cleans up resources.
|
||||
*/
|
||||
public stop(): void {
|
||||
if (this.intervalInstance !== null) {
|
||||
this.intervalInstance.cancel();
|
||||
this.intervalInstance = null;
|
||||
if (this._intervalInstance !== null) {
|
||||
this._intervalInstance.cancel();
|
||||
this._intervalInstance = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,25 +83,47 @@ export class Poller {
|
||||
*
|
||||
* @param intervalMs - The polling interval in milliseconds.
|
||||
*/
|
||||
private executePolling(intervalMs: number): void {
|
||||
if (this.intervalInstance !== null) {
|
||||
this.intervalInstance.cancel();
|
||||
private _executePolling(intervalMs: number): void {
|
||||
if (this._intervalInstance !== null) {
|
||||
this._intervalInstance.cancel();
|
||||
}
|
||||
|
||||
this.intervalInstance = interval(intervalMs, async () => {
|
||||
if (this.isExecuting) {
|
||||
if (intervalMs === 0) {
|
||||
this._executeSinglePoll();
|
||||
return;
|
||||
}
|
||||
|
||||
this.isExecuting = true;
|
||||
this._intervalInstance = interval(intervalMs, () => this._executePollingCycle());
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a single polling operation synchronously.
|
||||
*/
|
||||
private _executeSinglePoll(): void {
|
||||
try {
|
||||
this._pollingFunction();
|
||||
} catch (error) {
|
||||
console.error('Error during polling execution:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes an asynchronous polling cycle with execution guard.
|
||||
* Ensures only one polling cycle runs at a time using the isExecuting flag.
|
||||
*/
|
||||
private async _executePollingCycle(): Promise<void> {
|
||||
if (this._isExecuting) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._isExecuting = true;
|
||||
|
||||
try {
|
||||
await this.pollingFunction();
|
||||
await this._pollingFunction();
|
||||
} catch (error) {
|
||||
console.error('Error during polling execution:', error);
|
||||
} finally {
|
||||
this.isExecuting = false;
|
||||
}
|
||||
});
|
||||
this._isExecuting = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@ import { Gio } from 'astal/file';
|
||||
import { GLib } from 'astal/gobject';
|
||||
|
||||
declare global {
|
||||
const CONFIG: string;
|
||||
const CONFIG_DIR: string;
|
||||
const CONFIG_FILE: string;
|
||||
const TMP: string;
|
||||
const USER: string;
|
||||
const SRC_DIR: string;
|
||||
@@ -15,6 +16,20 @@ export function ensureDirectory(path: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
export function ensureJsonFile(path: string): void {
|
||||
const file = Gio.File.new_for_path(path);
|
||||
const parent = file.get_parent();
|
||||
|
||||
if (parent && !parent.query_exists(null)) {
|
||||
parent.make_directory_with_parents(null);
|
||||
}
|
||||
|
||||
if (!file.query_exists(null)) {
|
||||
const stream = file.create(Gio.FileCreateFlags.NONE, null);
|
||||
stream.write_all('{}', null);
|
||||
}
|
||||
}
|
||||
|
||||
export function ensureFile(path: string): void {
|
||||
const file = Gio.File.new_for_path(path);
|
||||
const parent = file.get_parent();
|
||||
@@ -31,12 +46,15 @@ export function ensureFile(path: string): void {
|
||||
const dataDir = typeof DATADIR !== 'undefined' ? DATADIR : SRC;
|
||||
|
||||
Object.assign(globalThis, {
|
||||
CONFIG: `${GLib.get_user_config_dir()}/hyprpanel/config.json`,
|
||||
CONFIG_DIR: `${GLib.get_user_config_dir()}/hyprpanel`,
|
||||
CONFIG_FILE: `${GLib.get_user_config_dir()}/hyprpanel/config.json`,
|
||||
TMP: `${GLib.get_tmp_dir()}/hyprpanel`,
|
||||
USER: GLib.get_user_name(),
|
||||
SRC_DIR: dataDir,
|
||||
});
|
||||
|
||||
ensureDirectory(TMP);
|
||||
ensureFile(CONFIG);
|
||||
ensureFile(CONFIG_FILE);
|
||||
ensureJsonFile(`${CONFIG_DIR}/modules.json`);
|
||||
ensureFile(`${CONFIG_DIR}/modules.scss`);
|
||||
App.add_icons(`${SRC_DIR}/assets`);
|
||||
|
||||
5
src/lib/types/bar.d.ts
vendored
5
src/lib/types/bar.d.ts
vendored
@@ -1,10 +1,12 @@
|
||||
import { Binding, Connectable } from 'types/service';
|
||||
import { Variable } from 'types/variable';
|
||||
import Box from 'types/widgets/box';
|
||||
import Button, { ButtonProps } from 'types/widgets/button';
|
||||
import Label from 'types/widgets/label';
|
||||
import { Attribute, Child } from './widget';
|
||||
import { Widget } from 'astal/gtk3';
|
||||
import { Binding } from 'astal';
|
||||
import { Connectable } from 'astal/binding';
|
||||
import { CustomBarModuleStyle } from 'src/components/bar/custom_modules/types';
|
||||
|
||||
export type BarBoxChild = {
|
||||
component: JSX.Element;
|
||||
@@ -25,6 +27,7 @@ export type BarModule = {
|
||||
textIcon?: string | Binding<string>;
|
||||
useTextIcon?: Binding<boolean>;
|
||||
label?: string | Binding<string>;
|
||||
truncationSize?: Binding<number>;
|
||||
labelHook?: LabelHook;
|
||||
boundLabel?: string;
|
||||
tooltipText?: string | Binding<string>;
|
||||
|
||||
2
src/lib/types/utils.d.ts
vendored
2
src/lib/types/utils.d.ts
vendored
@@ -9,3 +9,5 @@ export type ThrottleFn = (
|
||||
) => void;
|
||||
|
||||
export type ThrottleFnCallback = ((output: string) => void) | undefined;
|
||||
|
||||
export type Primitive = string | number | boolean | symbol | null | undefined | bigint;
|
||||
|
||||
@@ -12,9 +12,20 @@ import { Astal, Gdk, Gtk } from 'astal/gtk3';
|
||||
import AstalApps from 'gi://AstalApps?version=0.1';
|
||||
import { exec, execAsync } from 'astal/process';
|
||||
import AstalNotifd from 'gi://AstalNotifd?version=0.1';
|
||||
import { Primitive } from './types/utils';
|
||||
|
||||
const notifdService = AstalNotifd.get_default();
|
||||
|
||||
/**
|
||||
* Checks if a value is a primitive type.
|
||||
*
|
||||
* @param value - The value to check
|
||||
* @returns True if the value is a primitive (null, undefined, string, number, boolean, symbol, or bigint)
|
||||
*/
|
||||
export function isPrimitive(value: unknown): value is Primitive {
|
||||
return value === null || (typeof value !== 'object' && typeof value !== 'function');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles errors by throwing a new Error with a message.
|
||||
*
|
||||
@@ -102,7 +113,8 @@ export function icon(name: string | null, fallback = icons.missing): string {
|
||||
|
||||
if (lookUpIcon(icon)) return icon;
|
||||
|
||||
console.log(`no icon substitute "${icon}" for "${name}", fallback: "${fallback}"`);
|
||||
console.log(`No icon substitute "${icon}" for "${name}", fallback: "${fallback}"`);
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
@@ -154,9 +166,10 @@ export async function sh(cmd: string | string[]): Promise<string> {
|
||||
*
|
||||
* @returns An array of JSX elements, one for each monitor.
|
||||
*/
|
||||
export function forMonitors(widget: (monitor: number) => JSX.Element): JSX.Element[] {
|
||||
export async function forMonitors(widget: (monitor: number) => Promise<JSX.Element>): Promise<JSX.Element[]> {
|
||||
const n = Gdk.Display.get_default()?.get_n_monitors() || 1;
|
||||
return range(n, 0).flatMap(widget);
|
||||
|
||||
return Promise.all(range(n, 0).map(widget));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -91,6 +91,7 @@ export const resetCss = async (): Promise<void> => {
|
||||
const css = `${TMP}/main.css`;
|
||||
const scss = `${TMP}/entry.scss`;
|
||||
const localScss = `${SRC_DIR}/src/scss/main.scss`;
|
||||
const moduleScss = `${CONFIG_DIR}/modules.scss`;
|
||||
|
||||
const themeVariables = variables;
|
||||
const integratedVariables = themeVariables;
|
||||
@@ -102,6 +103,9 @@ export const resetCss = async (): Promise<void> => {
|
||||
let mainScss = readFile(localScss);
|
||||
mainScss = `${imports}\n${mainScss}`;
|
||||
|
||||
const moduleScssFile = readFile(moduleScss);
|
||||
mainScss = `${mainScss}\n${moduleScssFile}`;
|
||||
|
||||
writeFile(scss, mainScss);
|
||||
|
||||
await bash(`sass --load-path=${SRC_DIR}/src/scss ${scss} ${css}`);
|
||||
|
||||
@@ -32,22 +32,33 @@
|
||||
}
|
||||
}
|
||||
|
||||
$style-module-defaults: (
|
||||
'label-color': $bar-buttons-text,
|
||||
'icon-color': $bar-buttons-icon,
|
||||
'icon-background': $bar-buttons-icon_background,
|
||||
'label-background': $bar-buttons-background,
|
||||
'border-enabled': false,
|
||||
'border-color': $text,
|
||||
'icon-size': 1em,
|
||||
'inner-spacing': 0.5em,
|
||||
);
|
||||
|
||||
/*
|
||||
* #################################
|
||||
* # Styling Function #
|
||||
* #################################
|
||||
*/
|
||||
@mixin styleModule(
|
||||
$class,
|
||||
$textColor,
|
||||
$iconColor,
|
||||
$iconBackground,
|
||||
$labelBackground,
|
||||
$spacing,
|
||||
$borderEnabled,
|
||||
$borderColor,
|
||||
$fontSize: 1em
|
||||
) {
|
||||
@mixin styleModule($class, $config: ()) {
|
||||
$config: map-merge($style-module-defaults, $config);
|
||||
$text-color: map-get($config, 'text-color');
|
||||
$icon-color: map-get($config, 'icon-color');
|
||||
$icon-background: map-get($config, 'icon-background');
|
||||
$label-background: map-get($config, 'label-background');
|
||||
$spacing: map-get($config, 'inner-spacing');
|
||||
$border-enabled: map-get($config, 'border-enabled');
|
||||
$border-color: map-get($config, 'border-color');
|
||||
$icon-size: map-get($config, 'icon-size');
|
||||
|
||||
$bar-button-background-opacity-ratio: $bar-buttons-background_opacity * 0.01;
|
||||
$transparency-value: 1 - $bar-button-background-opacity-ratio;
|
||||
|
||||
@@ -56,12 +67,12 @@
|
||||
.bar_item_box_visible {
|
||||
&.#{$class} {
|
||||
background: transparentize(
|
||||
if($bar-buttons-monochrome, $bar-buttons-background, $labelBackground),
|
||||
if($bar-buttons-monochrome, $bar-buttons-background, $label-background),
|
||||
$transparency-value
|
||||
);
|
||||
border: if(
|
||||
$borderEnabled or $bar-buttons-enableBorders,
|
||||
$bar-buttons-borderSize solid if($bar-buttons-monochrome, $bar-buttons-borderColor, $borderColor),
|
||||
$border-enabled or $bar-buttons-enableBorders,
|
||||
$bar-buttons-borderSize solid if($bar-buttons-monochrome, $bar-buttons-borderColor, $border-color),
|
||||
0em
|
||||
);
|
||||
|
||||
@@ -76,35 +87,35 @@
|
||||
}
|
||||
|
||||
.module-label.#{$class} {
|
||||
color: if($bar-buttons-monochrome, $bar-buttons-text, $textColor);
|
||||
color: if($bar-buttons-monochrome, $bar-buttons-text, $text-color);
|
||||
margin-left: $spacing;
|
||||
border-radius: $bar-buttons-radius;
|
||||
}
|
||||
|
||||
.module-icon.#{$class} {
|
||||
color: if($bar-buttons-monochrome, $bar-buttons-icon, $iconColor);
|
||||
font-size: if($fontSize, $fontSize, 1em);
|
||||
color: if($bar-buttons-monochrome, $bar-buttons-icon, $icon-color);
|
||||
font-size: if($icon-size, $icon-size, 1em);
|
||||
}
|
||||
|
||||
.style2 {
|
||||
.module-icon.#{$class} {
|
||||
background: if($bar-buttons-monochrome, $bar-buttons-icon_background, $iconBackground);
|
||||
background: if($bar-buttons-monochrome, $bar-buttons-icon_background, $icon-background);
|
||||
padding-right: $spacing;
|
||||
color: if($bar-buttons-monochrome, $bar-buttons-icon, $iconColor);
|
||||
color: if($bar-buttons-monochrome, $bar-buttons-icon, $icon-color);
|
||||
border-top-left-radius: if(
|
||||
$borderEnabled or $bar-buttons-enableBorders,
|
||||
$border-enabled or $bar-buttons-enableBorders,
|
||||
$bar-buttons-radius * $bar-buttons-innerRadiusMultiplier,
|
||||
$bar-buttons-radius
|
||||
);
|
||||
border-bottom-left-radius: if(
|
||||
$borderEnabled or $bar-buttons-enableBorders,
|
||||
$border-enabled or $bar-buttons-enableBorders,
|
||||
$bar-buttons-radius * $bar-buttons-innerRadiusMultiplier,
|
||||
$bar-buttons-radius
|
||||
);
|
||||
}
|
||||
|
||||
.module-label.#{$class} {
|
||||
background: transparentize($labelBackground, $transparency-value);
|
||||
background: transparentize($label-background, $transparency-value);
|
||||
padding-left: $spacing * 1.5;
|
||||
margin-left: 0em;
|
||||
border-top-left-radius: 0em;
|
||||
@@ -113,12 +124,12 @@
|
||||
&.no-label.#{$class} {
|
||||
.module-icon {
|
||||
border-top-right-radius: if(
|
||||
$borderEnabled or $bar-buttons-enableBorders,
|
||||
$border-enabled or $bar-buttons-enableBorders,
|
||||
$bar-buttons-radius * $bar-buttons-innerRadiusMultiplier,
|
||||
$bar-buttons-radius
|
||||
);
|
||||
border-bottom-right-radius: if(
|
||||
$borderEnabled or $bar-buttons-enableBorders,
|
||||
$border-enabled or $bar-buttons-enableBorders,
|
||||
$bar-buttons-radius * $bar-buttons-innerRadiusMultiplier,
|
||||
$bar-buttons-radius
|
||||
);
|
||||
@@ -133,25 +144,17 @@
|
||||
* #################################
|
||||
*/
|
||||
@include styleModule(
|
||||
//
|
||||
// class name
|
||||
'mic',
|
||||
// label color
|
||||
$bar-buttons-modules-microphone-text,
|
||||
// icon color
|
||||
$bar-buttons-modules-microphone-icon,
|
||||
// icon background if split style is used
|
||||
$bar-buttons-modules-microphone-icon_background,
|
||||
// label background
|
||||
$bar-buttons-modules-microphone-background,
|
||||
// inner spacing
|
||||
$bar-buttons-modules-microphone-spacing,
|
||||
//
|
||||
// if border enabled
|
||||
$bar-buttons-modules-microphone-enableBorder,
|
||||
// border color
|
||||
$bar-buttons-modules-microphone-border,
|
||||
1.3em
|
||||
(
|
||||
'text-color': $bar-buttons-modules-microphone-text,
|
||||
'icon-color': $bar-buttons-modules-microphone-icon,
|
||||
'icon-background': $bar-buttons-modules-microphone-icon_background,
|
||||
'label-background': $bar-buttons-modules-microphone-background,
|
||||
'inner-spacing': $bar-buttons-modules-microphone-spacing,
|
||||
'border-enabled': $bar-buttons-modules-microphone-enableBorder,
|
||||
'border-color': $bar-buttons-modules-microphone-border,
|
||||
'icon-size': 1.3em,
|
||||
)
|
||||
);
|
||||
|
||||
/*
|
||||
@@ -160,24 +163,17 @@
|
||||
* #################################
|
||||
*/
|
||||
@include styleModule(
|
||||
//
|
||||
// class name
|
||||
'ram',
|
||||
// label color
|
||||
$bar-buttons-modules-ram-text,
|
||||
// icon color
|
||||
$bar-buttons-modules-ram-icon,
|
||||
// icon background if split style is used
|
||||
$bar-buttons-modules-ram-icon_background,
|
||||
// label background
|
||||
$bar-buttons-modules-ram-background,
|
||||
// inner spacing
|
||||
$bar-buttons-modules-ram-spacing,
|
||||
//
|
||||
// if border enabled
|
||||
$bar-buttons-modules-ram-enableBorder,
|
||||
// border color
|
||||
$bar-buttons-modules-ram-border
|
||||
(
|
||||
'text-color': $bar-buttons-modules-ram-text,
|
||||
'icon-color': $bar-buttons-modules-ram-icon,
|
||||
'icon-background': $bar-buttons-modules-ram-icon_background,
|
||||
'label-background': $bar-buttons-modules-ram-background,
|
||||
'inner-spacing': $bar-buttons-modules-ram-spacing,
|
||||
'border-enabled': $bar-buttons-modules-ram-enableBorder,
|
||||
'border-color': $bar-buttons-modules-ram-border,
|
||||
'icon-size': 1em,
|
||||
)
|
||||
);
|
||||
|
||||
/*
|
||||
@@ -186,25 +182,17 @@
|
||||
* #################################
|
||||
*/
|
||||
@include styleModule(
|
||||
//
|
||||
// class name
|
||||
'cpu',
|
||||
// label color
|
||||
$bar-buttons-modules-cpu-text,
|
||||
// icon color
|
||||
$bar-buttons-modules-cpu-icon,
|
||||
// icon background if split style is used
|
||||
$bar-buttons-modules-cpu-icon_background,
|
||||
// label background
|
||||
$bar-buttons-modules-cpu-background,
|
||||
// inner spacing
|
||||
$bar-buttons-modules-cpu-spacing,
|
||||
// if border enabled
|
||||
$bar-buttons-modules-cpu-enableBorder,
|
||||
// border color
|
||||
$bar-buttons-modules-cpu-border,
|
||||
// custom font size
|
||||
1.05em //
|
||||
(
|
||||
'text-color': $bar-buttons-modules-cpu-text,
|
||||
'icon-color': $bar-buttons-modules-cpu-icon,
|
||||
'icon-background': $bar-buttons-modules-cpu-icon_background,
|
||||
'label-background': $bar-buttons-modules-cpu-background,
|
||||
'inner-spacing': $bar-buttons-modules-cpu-spacing,
|
||||
'border-enabled': $bar-buttons-modules-cpu-enableBorder,
|
||||
'border-color': $bar-buttons-modules-cpu-border,
|
||||
'icon-size': 1.05em,
|
||||
)
|
||||
);
|
||||
|
||||
/*
|
||||
@@ -213,25 +201,17 @@
|
||||
* #################################
|
||||
*/
|
||||
@include styleModule(
|
||||
//
|
||||
// class name
|
||||
'cpu-temp',
|
||||
// label color
|
||||
$bar-buttons-modules-cpuTemp-text,
|
||||
// icon color
|
||||
$bar-buttons-modules-cpuTemp-icon,
|
||||
// icon background if split style is used
|
||||
$bar-buttons-modules-cpuTemp-icon_background,
|
||||
// label background
|
||||
$bar-buttons-modules-cpuTemp-background,
|
||||
// inner spacing
|
||||
$bar-buttons-modules-cpuTemp-spacing,
|
||||
// if border enabled
|
||||
$bar-buttons-modules-cpuTemp-enableBorder,
|
||||
// border color
|
||||
$bar-buttons-modules-cpuTemp-border,
|
||||
// custom font size
|
||||
1.05em //
|
||||
(
|
||||
'text-color': $bar-buttons-modules-cpuTemp-text,
|
||||
'icon-color': $bar-buttons-modules-cpuTemp-icon,
|
||||
'icon-background': $bar-buttons-modules-cpuTemp-icon_background,
|
||||
'label-background': $bar-buttons-modules-cpuTemp-background,
|
||||
'inner-spacing': $bar-buttons-modules-cpuTemp-spacing,
|
||||
'border-enabled': $bar-buttons-modules-cpuTemp-enableBorder,
|
||||
'border-color': $bar-buttons-modules-cpuTemp-border,
|
||||
'icon-size': 1.05em,
|
||||
)
|
||||
);
|
||||
|
||||
/*
|
||||
@@ -240,25 +220,17 @@
|
||||
* #################################
|
||||
*/
|
||||
@include styleModule(
|
||||
//
|
||||
// class name
|
||||
'storage',
|
||||
// label color
|
||||
$bar-buttons-modules-storage-text,
|
||||
// icon color
|
||||
$bar-buttons-modules-storage-icon,
|
||||
// icon background if split style is used
|
||||
$bar-buttons-modules-storage-icon_background,
|
||||
// label background
|
||||
$bar-buttons-modules-storage-background,
|
||||
// inner spacing
|
||||
$bar-buttons-modules-storage-spacing,
|
||||
// if border enabled
|
||||
$bar-buttons-modules-storage-enableBorder,
|
||||
// border color
|
||||
$bar-buttons-modules-storage-border,
|
||||
// custom font size
|
||||
1.3em //
|
||||
(
|
||||
'text-color': $bar-buttons-modules-storage-text,
|
||||
'icon-color': $bar-buttons-modules-storage-icon,
|
||||
'icon-background': $bar-buttons-modules-storage-icon_background,
|
||||
'label-background': $bar-buttons-modules-storage-background,
|
||||
'inner-spacing': $bar-buttons-modules-storage-spacing,
|
||||
'border-enabled': $bar-buttons-modules-storage-enableBorder,
|
||||
'border-color': $bar-buttons-modules-storage-border,
|
||||
'icon-size': 1.3em,
|
||||
)
|
||||
);
|
||||
|
||||
/*
|
||||
@@ -267,25 +239,17 @@
|
||||
* #################################
|
||||
*/
|
||||
@include styleModule(
|
||||
//
|
||||
// class name
|
||||
'netstat',
|
||||
// label color
|
||||
$bar-buttons-modules-netstat-text,
|
||||
// icon color
|
||||
$bar-buttons-modules-netstat-icon,
|
||||
// icon background if split style is used
|
||||
$bar-buttons-modules-netstat-icon_background,
|
||||
// label background
|
||||
$bar-buttons-modules-netstat-background,
|
||||
// inner spacing
|
||||
$bar-buttons-modules-netstat-spacing,
|
||||
// if border enabled
|
||||
$bar-buttons-modules-netstat-enableBorder,
|
||||
// border color
|
||||
$bar-buttons-modules-netstat-border,
|
||||
// custom font size
|
||||
1.2em //
|
||||
(
|
||||
'text-color': $bar-buttons-modules-netstat-text,
|
||||
'icon-color': $bar-buttons-modules-netstat-icon,
|
||||
'icon-background': $bar-buttons-modules-netstat-icon_background,
|
||||
'label-background': $bar-buttons-modules-netstat-background,
|
||||
'inner-spacing': $bar-buttons-modules-netstat-spacing,
|
||||
'border-enabled': $bar-buttons-modules-netstat-enableBorder,
|
||||
'border-color': $bar-buttons-modules-netstat-border,
|
||||
'icon-size': 1.2em,
|
||||
)
|
||||
);
|
||||
|
||||
/*
|
||||
@@ -294,25 +258,17 @@
|
||||
* #################################
|
||||
*/
|
||||
@include styleModule(
|
||||
//
|
||||
// class name
|
||||
'kblayout',
|
||||
// label color
|
||||
$bar-buttons-modules-kbLayout-text,
|
||||
// icon color
|
||||
$bar-buttons-modules-kbLayout-icon,
|
||||
// icon background if split style is used
|
||||
$bar-buttons-modules-kbLayout-icon_background,
|
||||
// label background
|
||||
$bar-buttons-modules-kbLayout-background,
|
||||
// inner spacing
|
||||
$bar-buttons-modules-kbLayout-spacing,
|
||||
// if border enabled
|
||||
$bar-buttons-modules-kbLayout-enableBorder,
|
||||
// border color
|
||||
$bar-buttons-modules-kbLayout-border,
|
||||
// custom font size
|
||||
1.2em //
|
||||
(
|
||||
'text-color': $bar-buttons-modules-kbLayout-text,
|
||||
'icon-color': $bar-buttons-modules-kbLayout-icon,
|
||||
'icon-background': $bar-buttons-modules-kbLayout-icon_background,
|
||||
'label-background': $bar-buttons-modules-kbLayout-background,
|
||||
'inner-spacing': $bar-buttons-modules-kbLayout-spacing,
|
||||
'border-enabled': $bar-buttons-modules-kbLayout-enableBorder,
|
||||
'border-color': $bar-buttons-modules-kbLayout-border,
|
||||
'icon-size': 1.2em,
|
||||
)
|
||||
);
|
||||
|
||||
/*
|
||||
@@ -321,25 +277,17 @@
|
||||
* #################################
|
||||
*/
|
||||
@include styleModule(
|
||||
//
|
||||
// class name
|
||||
'updates',
|
||||
// label color
|
||||
$bar-buttons-modules-updates-text,
|
||||
// icon color
|
||||
$bar-buttons-modules-updates-icon,
|
||||
// icon background if split style is used
|
||||
$bar-buttons-modules-updates-icon_background,
|
||||
// label background
|
||||
$bar-buttons-modules-updates-background,
|
||||
// inner spacing
|
||||
$bar-buttons-modules-updates-spacing,
|
||||
// if border enabled
|
||||
$bar-buttons-modules-updates-enableBorder,
|
||||
// border color
|
||||
$bar-buttons-modules-updates-border,
|
||||
// custom font size
|
||||
1.2em //
|
||||
(
|
||||
'text-color': $bar-buttons-modules-updates-text,
|
||||
'icon-color': $bar-buttons-modules-updates-icon,
|
||||
'icon-background': $bar-buttons-modules-updates-icon_background,
|
||||
'label-background': $bar-buttons-modules-updates-background,
|
||||
'inner-spacing': $bar-buttons-modules-updates-spacing,
|
||||
'border-enabled': $bar-buttons-modules-updates-enableBorder,
|
||||
'border-color': $bar-buttons-modules-updates-border,
|
||||
'icon-size': 1.2em,
|
||||
)
|
||||
);
|
||||
|
||||
/*
|
||||
@@ -348,25 +296,17 @@
|
||||
* #################################
|
||||
*/
|
||||
@include styleModule(
|
||||
//
|
||||
// class name
|
||||
'submap',
|
||||
// label color
|
||||
$bar-buttons-modules-submap-text,
|
||||
// icon color
|
||||
$bar-buttons-modules-submap-icon,
|
||||
// icon background if split style is used
|
||||
$bar-buttons-modules-submap-icon_background,
|
||||
// label background
|
||||
$bar-buttons-modules-submap-background,
|
||||
// inner spacing
|
||||
$bar-buttons-modules-submap-spacing,
|
||||
// if border enabled
|
||||
$bar-buttons-modules-submap-enableBorder,
|
||||
// border color
|
||||
$bar-buttons-modules-submap-border,
|
||||
// custom font size
|
||||
1.2em //
|
||||
(
|
||||
'text-color': $bar-buttons-modules-submap-text,
|
||||
'icon-color': $bar-buttons-modules-submap-icon,
|
||||
'icon-background': $bar-buttons-modules-submap-icon_background,
|
||||
'label-background': $bar-buttons-modules-submap-background,
|
||||
'inner-spacing': $bar-buttons-modules-submap-spacing,
|
||||
'border-enabled': $bar-buttons-modules-submap-enableBorder,
|
||||
'border-color': $bar-buttons-modules-submap-border,
|
||||
'icon-size': 1.2em,
|
||||
)
|
||||
);
|
||||
|
||||
/*
|
||||
@@ -375,25 +315,17 @@
|
||||
* #################################
|
||||
*/
|
||||
@include styleModule(
|
||||
//
|
||||
// class name
|
||||
'weather-custom',
|
||||
// label color
|
||||
$bar-buttons-modules-weather-text,
|
||||
// icon color
|
||||
$bar-buttons-modules-weather-icon,
|
||||
// icon background if split style is used
|
||||
$bar-buttons-modules-weather-icon_background,
|
||||
// label background
|
||||
$bar-buttons-modules-weather-background,
|
||||
// inner spacing
|
||||
$bar-buttons-modules-weather-spacing,
|
||||
// if border enabled
|
||||
$bar-buttons-modules-weather-enableBorder,
|
||||
// border color
|
||||
$bar-buttons-modules-weather-border,
|
||||
// custom font size
|
||||
1.2em //
|
||||
(
|
||||
'text-color': $bar-buttons-modules-weather-text,
|
||||
'icon-color': $bar-buttons-modules-weather-icon,
|
||||
'icon-background': $bar-buttons-modules-weather-icon_background,
|
||||
'label-background': $bar-buttons-modules-weather-background,
|
||||
'inner-spacing': $bar-buttons-modules-weather-spacing,
|
||||
'border-enabled': $bar-buttons-modules-weather-enableBorder,
|
||||
'border-color': $bar-buttons-modules-weather-border,
|
||||
'icon-size': 1.2em,
|
||||
)
|
||||
);
|
||||
|
||||
/*
|
||||
@@ -402,25 +334,17 @@
|
||||
* #################################
|
||||
*/
|
||||
@include styleModule(
|
||||
//
|
||||
// class name
|
||||
'powermodule',
|
||||
// label color
|
||||
$red,
|
||||
// icon color
|
||||
$bar-buttons-modules-power-icon,
|
||||
// icon background if split style is used
|
||||
$bar-buttons-modules-power-icon_background,
|
||||
// label background
|
||||
$bar-buttons-modules-power-background,
|
||||
// inner spacing
|
||||
$bar-buttons-modules-power-spacing,
|
||||
// if border enabled
|
||||
$bar-buttons-modules-power-enableBorder,
|
||||
// border color
|
||||
$bar-buttons-modules-power-border,
|
||||
// custom font size
|
||||
1.3em //
|
||||
(
|
||||
'text-color': $red,
|
||||
'icon-color': $bar-buttons-modules-power-icon,
|
||||
'icon-background': $bar-buttons-modules-power-icon_background,
|
||||
'label-background': $bar-buttons-modules-power-background,
|
||||
'inner-spacing': $bar-buttons-modules-power-spacing,
|
||||
'border-enabled': $bar-buttons-modules-power-enableBorder,
|
||||
'border-color': $bar-buttons-modules-power-border,
|
||||
'icon-size': 1.3em,
|
||||
)
|
||||
);
|
||||
|
||||
/*
|
||||
@@ -429,25 +353,17 @@
|
||||
* #################################
|
||||
*/
|
||||
@include styleModule(
|
||||
//
|
||||
// class name
|
||||
'hyprsunset',
|
||||
// label color
|
||||
$bar-buttons-modules-hyprsunset-text,
|
||||
// icon color
|
||||
$bar-buttons-modules-hyprsunset-icon,
|
||||
// icon background if split style is used
|
||||
$bar-buttons-modules-hyprsunset-icon_background,
|
||||
// label background
|
||||
$bar-buttons-modules-hyprsunset-background,
|
||||
// inner spacing
|
||||
$bar-buttons-modules-hyprsunset-spacing,
|
||||
// if border enabled
|
||||
$bar-buttons-modules-hyprsunset-enableBorder,
|
||||
// border color
|
||||
$bar-buttons-modules-hyprsunset-border,
|
||||
// custom font size
|
||||
1.3em //
|
||||
(
|
||||
'text-color': $bar-buttons-modules-hyprsunset-text,
|
||||
'icon-color': $bar-buttons-modules-hyprsunset-icon,
|
||||
'icon-background': $bar-buttons-modules-hyprsunset-icon_background,
|
||||
'label-background': $bar-buttons-modules-hyprsunset-background,
|
||||
'inner-spacing': $bar-buttons-modules-hyprsunset-spacing,
|
||||
'border-enabled': $bar-buttons-modules-hyprsunset-enableBorder,
|
||||
'border-color': $bar-buttons-modules-hyprsunset-border,
|
||||
'icon-size': 1.3em,
|
||||
)
|
||||
);
|
||||
|
||||
/*
|
||||
@@ -456,25 +372,17 @@
|
||||
* #################################
|
||||
*/
|
||||
@include styleModule(
|
||||
//
|
||||
// class name
|
||||
'hypridle',
|
||||
// label color
|
||||
$bar-buttons-modules-hypridle-text,
|
||||
// icon color
|
||||
$bar-buttons-modules-hypridle-icon,
|
||||
// icon background if split style is used
|
||||
$bar-buttons-modules-hypridle-icon_background,
|
||||
// label background
|
||||
$bar-buttons-modules-hypridle-background,
|
||||
// inner spacing
|
||||
$bar-buttons-modules-hypridle-spacing,
|
||||
// if border enabled
|
||||
$bar-buttons-modules-hypridle-enableBorder,
|
||||
// border color
|
||||
$bar-buttons-modules-hypridle-border,
|
||||
// custom font size
|
||||
1.075em //
|
||||
(
|
||||
'text-color': $bar-buttons-modules-hypridle-text,
|
||||
'icon-color': $bar-buttons-modules-hypridle-icon,
|
||||
'icon-background': $bar-buttons-modules-hypridle-icon_background,
|
||||
'label-background': $bar-buttons-modules-hypridle-background,
|
||||
'inner-spacing': $bar-buttons-modules-hypridle-spacing,
|
||||
'border-enabled': $bar-buttons-modules-hypridle-enableBorder,
|
||||
'border-color': $bar-buttons-modules-hypridle-border,
|
||||
'icon-size': 1.075em,
|
||||
)
|
||||
);
|
||||
|
||||
/*
|
||||
@@ -483,25 +391,17 @@
|
||||
* #################################
|
||||
*/
|
||||
@include styleModule(
|
||||
//
|
||||
// class name
|
||||
'cava',
|
||||
// label color
|
||||
$bar-buttons-modules-cava-text,
|
||||
// icon color
|
||||
$bar-buttons-modules-cava-icon,
|
||||
// icon background if split style is used
|
||||
$bar-buttons-modules-cava-icon_background,
|
||||
// label background
|
||||
$bar-buttons-modules-cava-background,
|
||||
// inner spacing
|
||||
$bar-buttons-modules-cava-spacing,
|
||||
// if border enabled
|
||||
$bar-buttons-modules-cava-enableBorder,
|
||||
// border color
|
||||
$bar-buttons-modules-cava-border,
|
||||
// custom font size
|
||||
1.2em //
|
||||
(
|
||||
'text-color': $bar-buttons-modules-cava-text,
|
||||
'icon-color': $bar-buttons-modules-cava-icon,
|
||||
'icon-background': $bar-buttons-modules-cava-icon_background,
|
||||
'label-background': $bar-buttons-modules-cava-background,
|
||||
'inner-spacing': $bar-buttons-modules-cava-spacing,
|
||||
'border-enabled': $bar-buttons-modules-cava-enableBorder,
|
||||
'border-color': $bar-buttons-modules-cava-border,
|
||||
'icon-size': 1.2em,
|
||||
)
|
||||
);
|
||||
|
||||
/*
|
||||
@@ -510,23 +410,15 @@
|
||||
* #################################
|
||||
*/
|
||||
@include styleModule(
|
||||
//
|
||||
// class name
|
||||
'worldclock',
|
||||
// label color
|
||||
$bar-buttons-modules-worldclock-text,
|
||||
// icon color
|
||||
$bar-buttons-modules-worldclock-icon,
|
||||
// icon background if split style is used
|
||||
$bar-buttons-modules-worldclock-icon_background,
|
||||
// label background
|
||||
$bar-buttons-modules-worldclock-background,
|
||||
// inner spacing
|
||||
$bar-buttons-modules-worldclock-spacing,
|
||||
// if border enabled
|
||||
$bar-buttons-modules-worldclock-enableBorder,
|
||||
// border color
|
||||
$bar-buttons-modules-worldclock-border,
|
||||
// custom font size
|
||||
1.2em //
|
||||
(
|
||||
'text-color': $bar-buttons-modules-worldclock-text,
|
||||
'icon-color': $bar-buttons-modules-worldclock-icon,
|
||||
'icon-background': $bar-buttons-modules-worldclock-icon_background,
|
||||
'label-background': $bar-buttons-modules-worldclock-background,
|
||||
'inner-spacing': $bar-buttons-modules-worldclock-spacing,
|
||||
'border-enabled': $bar-buttons-modules-worldclock-enableBorder,
|
||||
'border-color': $bar-buttons-modules-worldclock-border,
|
||||
'icon-size': 1.2em,
|
||||
)
|
||||
);
|
||||
|
||||
@@ -12,6 +12,7 @@ export const initializeHotReload = async (): Promise<void> => {
|
||||
`${SRC_DIR}/src/scss/style/settings`,
|
||||
`${SRC_DIR}/src/scss/style/colors.scss`,
|
||||
`${SRC_DIR}/src/scss/style/highlights.scss`,
|
||||
`${CONFIG_DIR}/modules.scss`,
|
||||
];
|
||||
|
||||
monitorList.forEach((file) => monitorFile(file, resetCss));
|
||||
|
||||
Reference in New Issue
Block a user