Feat: Add live reloading of configuration file (#684)
* Add live reloading of configuration file This also removes the need for a file with all the available configuration and a shadow configuration file. Additionally, added several improvements: 1. Reduce I/O on initial configuration loading by only reading file once 2. Remove unnecesary back and forth events when editing configuration * Add missing return type * Consistently reset on config changes and error if failed to initialize config * Fix massive I/O load on startup by numerical options * Use _findVal when monitoring config file * Apply PR requested changes Signed-off-by: davfsa <davfsa@gmail.com> * Add missing => Signed-off-by: davfsa <davfsa@gmail.com> * Fix reassignment to const, change to let. --------- Signed-off-by: davfsa <davfsa@gmail.com> Co-authored-by: Jas Singh <jaskiratpal.singh@outlook.com>
This commit is contained in:
@@ -272,32 +272,23 @@ export const importFiles = (themeOnly: boolean = false): void => {
|
|||||||
iconName: icons.ui.info,
|
iconName: icons.ui.info,
|
||||||
});
|
});
|
||||||
|
|
||||||
const tmpConfigFile = Gio.File.new_for_path(`${TMP}/config.json`);
|
|
||||||
const optionsConfigFile = Gio.File.new_for_path(CONFIG);
|
const optionsConfigFile = Gio.File.new_for_path(CONFIG);
|
||||||
|
|
||||||
const [tmpSuccess, tmpContent] = tmpConfigFile.load_contents(null);
|
|
||||||
const [optionsSuccess, optionsContent] = optionsConfigFile.load_contents(null);
|
const [optionsSuccess, optionsContent] = optionsConfigFile.load_contents(null);
|
||||||
|
|
||||||
if (!tmpSuccess || !optionsSuccess) {
|
if (!optionsSuccess) {
|
||||||
console.error('Failed to read existing configuration files.');
|
console.error('Failed to read existing configuration file.');
|
||||||
dialog.destroy();
|
dialog.destroy();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let tmpConfig = JSON.parse(new TextDecoder('utf-8').decode(tmpContent));
|
|
||||||
let optionsConfig = JSON.parse(new TextDecoder('utf-8').decode(optionsContent));
|
let optionsConfig = JSON.parse(new TextDecoder('utf-8').decode(optionsContent));
|
||||||
|
|
||||||
if (themeOnly) {
|
const filteredConfig = themeOnly
|
||||||
const filteredConfig = filterConfigForThemeOnly(importedConfig);
|
? filterConfigForThemeOnly(importedConfig)
|
||||||
tmpConfig = { ...tmpConfig, ...filteredConfig };
|
: filterConfigForNonTheme(importedConfig);
|
||||||
optionsConfig = { ...optionsConfig, ...filteredConfig };
|
optionsConfig = { ...optionsConfig, ...filteredConfig };
|
||||||
} else {
|
|
||||||
const filteredConfig = filterConfigForNonTheme(importedConfig);
|
|
||||||
tmpConfig = { ...tmpConfig, ...filteredConfig };
|
|
||||||
optionsConfig = { ...optionsConfig, ...filteredConfig };
|
|
||||||
}
|
|
||||||
|
|
||||||
saveConfigToFile(tmpConfig, `${TMP}/config.json`);
|
|
||||||
saveConfigToFile(optionsConfig, CONFIG);
|
saveConfigToFile(optionsConfig, CONFIG);
|
||||||
}
|
}
|
||||||
dialog.destroy();
|
dialog.destroy();
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ export const BooleanInputter = <T extends string | number | boolean | object>({
|
|||||||
|
|
||||||
interface BooleanInputterProps<T> {
|
interface BooleanInputterProps<T> {
|
||||||
opt: Opt<T>;
|
opt: Opt<T>;
|
||||||
isUnsaved?: Variable<boolean>;
|
|
||||||
disabledBinding?: Variable<boolean>;
|
disabledBinding?: Variable<boolean>;
|
||||||
dependencies?: string[];
|
dependencies?: string[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,14 +29,18 @@ export const NumberInputter = <T extends string | number | boolean | object>({
|
|||||||
})}
|
})}
|
||||||
</box>
|
</box>
|
||||||
<SpinButton
|
<SpinButton
|
||||||
|
onChanged={(self) => {
|
||||||
|
const currentText = self.value;
|
||||||
|
const optValue = opt.get();
|
||||||
|
isUnsaved.set(currentText !== optValue);
|
||||||
|
}}
|
||||||
|
onActivate={(self) => {
|
||||||
|
opt.set(self.value as T);
|
||||||
|
}}
|
||||||
setup={(self) => {
|
setup={(self) => {
|
||||||
self.set_range(min, max);
|
self.set_range(min, max);
|
||||||
self.set_increments(1 * increment, 5 * increment);
|
self.set_increments(1 * increment, 5 * increment);
|
||||||
|
|
||||||
self.connect('value-changed', () => {
|
|
||||||
opt.set(self.value as T);
|
|
||||||
});
|
|
||||||
|
|
||||||
useHook(self, opt, () => {
|
useHook(self, opt, () => {
|
||||||
self.set_value(opt.get() as number);
|
self.set_value(opt.get() as number);
|
||||||
isUnsaved.set(Number(self.get_text()) !== opt.get());
|
isUnsaved.set(Number(self.get_text()) !== opt.get());
|
||||||
|
|||||||
@@ -14,24 +14,19 @@ globalThis.useTheme = (filePath: string): void => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tmpConfigFile = Gio.File.new_for_path(`${TMP}/config.json`);
|
|
||||||
const optionsConfigFile = Gio.File.new_for_path(CONFIG);
|
const optionsConfigFile = Gio.File.new_for_path(CONFIG);
|
||||||
|
|
||||||
const [tmpSuccess, tmpContent] = tmpConfigFile.load_contents(null);
|
|
||||||
const [optionsSuccess, optionsContent] = optionsConfigFile.load_contents(null);
|
const [optionsSuccess, optionsContent] = optionsConfigFile.load_contents(null);
|
||||||
|
|
||||||
if (!tmpSuccess || !optionsSuccess) {
|
if (!optionsSuccess) {
|
||||||
throw new Error('Failed to load theme file.');
|
throw new Error('Failed to load theme file.');
|
||||||
}
|
}
|
||||||
|
|
||||||
let tmpConfig = JSON.parse(new TextDecoder('utf-8').decode(tmpContent));
|
|
||||||
let optionsConfig = JSON.parse(new TextDecoder('utf-8').decode(optionsContent));
|
let optionsConfig = JSON.parse(new TextDecoder('utf-8').decode(optionsContent));
|
||||||
|
|
||||||
const filteredConfig = filterConfigForThemeOnly(importedConfig);
|
const filteredConfig = filterConfigForThemeOnly(importedConfig);
|
||||||
tmpConfig = { ...tmpConfig, ...filteredConfig };
|
|
||||||
optionsConfig = { ...optionsConfig, ...filteredConfig };
|
optionsConfig = { ...optionsConfig, ...filteredConfig };
|
||||||
|
|
||||||
saveConfigToFile(tmpConfig, `${TMP}/config.json`);
|
|
||||||
saveConfigToFile(optionsConfig, CONFIG);
|
saveConfigToFile(optionsConfig, CONFIG);
|
||||||
bash(restartCommand.get());
|
bash(restartCommand.get());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,20 +1,17 @@
|
|||||||
import { isHexColor } from '../globals/variables';
|
|
||||||
import { MkOptionsResult } from './types/options';
|
import { MkOptionsResult } from './types/options';
|
||||||
import { ensureDirectory } from './session';
|
|
||||||
import Variable from 'astal/variable';
|
import Variable from 'astal/variable';
|
||||||
import { monitorFile, readFile, writeFile } from 'astal/file';
|
import { monitorFile, readFile, writeFile } from 'astal/file';
|
||||||
import GLib from 'gi://GLib?version=2.0';
|
import { errorHandler, Notify } from './utils';
|
||||||
import { errorHandler } from './utils';
|
import { ensureDirectory } from './session';
|
||||||
|
import icons from './icons/icons';
|
||||||
|
|
||||||
type OptProps = {
|
type OptProps = {
|
||||||
persistent?: boolean;
|
persistent?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
type WriteDiskProps = {
|
||||||
* A file to store default configurations. Placed inside the cache directory.
|
writeDisk?: boolean;
|
||||||
* NOTE: We need to move this out into the .config directory instead.
|
};
|
||||||
*/
|
|
||||||
export const defaultFile = `${GLib.get_tmp_dir()}/ags/hyprpanel/default.json`;
|
|
||||||
|
|
||||||
export class Opt<T = unknown> extends Variable<T> {
|
export class Opt<T = unknown> extends Variable<T> {
|
||||||
/**
|
/**
|
||||||
@@ -76,118 +73,109 @@ export class Opt<T = unknown> extends Variable<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes this option by attempting to read its value from a cache file.
|
* Initializes this option based on the provided configuration, if available.
|
||||||
* If found, sets the current value. Also sets up a subscription to write updates back.
|
|
||||||
*
|
*
|
||||||
* @param cacheFile - The path to the cache file.
|
* @param config - The configuration.
|
||||||
*/
|
*/
|
||||||
public init(cacheFile: string): void {
|
public init(config: Record<string, unknown>): void {
|
||||||
const rawData = readFile(cacheFile);
|
const value = _findVal(config, this._id.split('.'));
|
||||||
|
|
||||||
let cacheData: Record<string, unknown> = {};
|
if (value !== undefined) {
|
||||||
|
this.set(value as T, { writeDisk: false });
|
||||||
if (rawData && rawData.trim() !== '') {
|
|
||||||
try {
|
|
||||||
cacheData = JSON.parse(rawData) as Record<string, unknown>;
|
|
||||||
} catch (error) {
|
|
||||||
errorHandler(error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const cachedVariable = this._findKey(cacheData, this._id.split('.'));
|
|
||||||
|
|
||||||
if (cachedVariable !== undefined) {
|
|
||||||
this.set(cachedVariable as T);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.subscribe((newVal) => {
|
|
||||||
const reRaw = readFile(cacheFile);
|
|
||||||
let currentCache: Record<string, unknown> = {};
|
|
||||||
if (reRaw && reRaw.trim() !== '') {
|
|
||||||
try {
|
|
||||||
currentCache = JSON.parse(reRaw) as Record<string, unknown>;
|
|
||||||
} catch {
|
|
||||||
// Do nuffin
|
|
||||||
}
|
|
||||||
}
|
|
||||||
currentCache[this._id] = newVal;
|
|
||||||
writeFile(cacheFile, JSON.stringify(currentCache, null, 2));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes this option by attempting to read its default value from the default file.
|
* Set the given configuration value and write it to disk, if specified.
|
||||||
* If found, sets the current value.
|
*
|
||||||
|
* @param value - The new value.
|
||||||
|
* @param writeDisk - Whether to write the changes to disk. Defaults to true.
|
||||||
*/
|
*/
|
||||||
public createDefault(): void {
|
public set = (value: T, { writeDisk = true }: WriteDiskProps = {}): void => {
|
||||||
const rawData = readFile(defaultFile);
|
if (value === this.get()) {
|
||||||
|
// If nothing actually changed, exit quick
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let defaultData: Record<string, unknown> = {};
|
super.set(value);
|
||||||
|
|
||||||
if (rawData && rawData.trim() !== '') {
|
if (writeDisk) {
|
||||||
try {
|
const raw = readFile(CONFIG);
|
||||||
defaultData = JSON.parse(rawData) as Record<string, unknown>;
|
let config: Record<string, unknown> = {};
|
||||||
} catch {
|
if (raw && raw.trim() !== '') {
|
||||||
// do nuffin
|
try {
|
||||||
|
config = JSON.parse(raw) as Record<string, unknown>;
|
||||||
|
} catch (error) {
|
||||||
|
// Last thing we want is to reset someones entire config
|
||||||
|
// so notify them instead
|
||||||
|
console.error(`Failed to load config file: ${error}`);
|
||||||
|
Notify({
|
||||||
|
summary: 'Failed to load config file',
|
||||||
|
body: `${error}`,
|
||||||
|
iconName: icons.ui.warning,
|
||||||
|
});
|
||||||
|
|
||||||
|
errorHandler(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
config[this._id] = value;
|
||||||
|
writeFile(CONFIG, JSON.stringify(config, null, 2));
|
||||||
}
|
}
|
||||||
|
};
|
||||||
const defaultVal = defaultData[this._id];
|
|
||||||
|
|
||||||
if (defaultVal !== undefined) {
|
|
||||||
this.set(defaultVal as T);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets the value of this option to its initial value if not persistent and if it differs from the current value.
|
* Resets the value of this option to its initial value if not persistent and if it differs from the current value.
|
||||||
*
|
*
|
||||||
|
* @param writeDisk - Whether to write the changes to disk. Defaults to true.
|
||||||
* @returns Returns the option's ID if reset occurred, otherwise undefined.
|
* @returns Returns the option's ID if reset occurred, otherwise undefined.
|
||||||
*/
|
*/
|
||||||
public reset(): string | undefined {
|
public reset(writeDiskProps: WriteDiskProps = {}): string | undefined {
|
||||||
if (this.persistent) {
|
if (this.persistent) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const current = this.get();
|
let currentValue: string | T = this.get();
|
||||||
|
currentValue = typeof currentValue === 'object' ? JSON.stringify(currentValue) : currentValue;
|
||||||
|
let initialValue: string | T = this.initial;
|
||||||
|
initialValue = typeof initialValue === 'object' ? JSON.stringify(initialValue) : initialValue;
|
||||||
|
|
||||||
if (JSON.stringify(current) !== JSON.stringify(this.initial)) {
|
if (currentValue !== initialValue) {
|
||||||
this.set(this.initial);
|
this.set(this.initial, writeDiskProps);
|
||||||
return this._id;
|
return this._id;
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _findKey(obj: Record<string, unknown>, path: string[]): T | undefined {
|
function _findVal(obj: Record<string, unknown>, path: string[]): unknown | undefined {
|
||||||
const top = path.shift();
|
const top = path.shift();
|
||||||
|
|
||||||
if (!top) {
|
if (!top) {
|
||||||
// The path is empty, so this is our value.
|
// The path is empty, so this is our value.
|
||||||
return obj as T;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof obj !== 'object') {
|
if (typeof obj !== 'object') {
|
||||||
// Not an array, not an object, but we need to go deeper.
|
// Not an array, not an object, but we need to go deeper.
|
||||||
// This is invalid, so return.
|
// This is invalid, so return.
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mergedPath = [top, ...path].join('.');
|
|
||||||
|
|
||||||
if (mergedPath in obj) {
|
|
||||||
// The key exists on this level with dot-notation, so we return that.
|
|
||||||
return obj[mergedPath] as T;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (top in obj) {
|
|
||||||
// The value exists but we are not there yet, so we recurse.
|
|
||||||
return this._findKey(obj[top] as Record<string, unknown>, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Key does not exist :(
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mergedPath = [top, ...path].join('.');
|
||||||
|
|
||||||
|
if (mergedPath in obj) {
|
||||||
|
// The key exists on this level with dot-notation, so we return that.
|
||||||
|
return obj[mergedPath];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (top in obj) {
|
||||||
|
// The value exists but we are not there yet, so we recurse.
|
||||||
|
return _findVal(obj[top] as Record<string, unknown>, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key does not exist :(
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -203,15 +191,15 @@ export function opt<T>(initial: T, props?: OptProps): Opt<T> {
|
|||||||
/**
|
/**
|
||||||
* Recursively traverses the provided object to extract all `Opt` instances, assigning IDs to each.
|
* Recursively traverses the provided object to extract all `Opt` instances, assigning IDs to each.
|
||||||
*
|
*
|
||||||
* @param object - The object containing `Opt` instances.
|
* @param optionsObj - The object containing `Opt` instances.
|
||||||
* @param [path=''] - The current path (used internally).
|
* @param [path=''] - The current path (used internally).
|
||||||
* @param [arr=[]] - The accumulator array for found `Opt` instances.
|
* @param [arr=[]] - The accumulator array for found `Opt` instances.
|
||||||
* @returns An array of all found `Opt` instances.
|
* @returns An array of all found `Opt` instances.
|
||||||
*/
|
*/
|
||||||
function getOptions(object: Record<string, unknown>, path = '', arr: Opt[] = []): Opt[] {
|
function getOptions(optionsObj: Record<string, unknown>, path = '', arr: Opt[] = []): Opt[] {
|
||||||
try {
|
try {
|
||||||
for (const key in object) {
|
for (const key in optionsObj) {
|
||||||
const value = object[key];
|
const value = optionsObj[key];
|
||||||
const id = path ? `${path}.${key}` : key;
|
const id = path ? `${path}.${key}` : key;
|
||||||
|
|
||||||
if (value instanceof Variable) {
|
if (value instanceof Variable) {
|
||||||
@@ -233,66 +221,78 @@ function getOptions(object: Record<string, unknown>, path = '', arr: Opt[] = [])
|
|||||||
* includes methods to reset values, reset theme colors, and handle dependencies.
|
* includes methods to reset values, reset theme colors, and handle dependencies.
|
||||||
*
|
*
|
||||||
* @template T extends object
|
* @template T extends object
|
||||||
* @param cacheFile - The file path to store cached values.
|
* @param optionsObj - The object containing nested `Opt` instances.
|
||||||
* @param object - The object containing nested `Opt` instances.
|
|
||||||
* @param [confFile='config.json'] - The configuration file name stored in TMP.
|
|
||||||
* @returns The original object extended with additional methods for handling options.
|
* @returns The original object extended with additional methods for handling options.
|
||||||
*/
|
*/
|
||||||
export function mkOptions<T extends object>(
|
export function mkOptions<T extends object>(optionsObj: T): T & MkOptionsResult {
|
||||||
cacheFile: string,
|
ensureDirectory(CONFIG.split('/').slice(0, -1).join('/'));
|
||||||
object: T,
|
|
||||||
confFile: string = 'config.json',
|
|
||||||
): T & MkOptionsResult {
|
|
||||||
const allOptions = getOptions(object as Record<string, unknown>);
|
|
||||||
|
|
||||||
for (let i = 0; i < allOptions.length; i++) {
|
const rawConfig = readFile(CONFIG);
|
||||||
allOptions[i].init(cacheFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
ensureDirectory(cacheFile.split('/').slice(0, -1).join('/'));
|
let config: Record<string, unknown> = {};
|
||||||
ensureDirectory(defaultFile.split('/').slice(0, -1).join('/'));
|
if (rawConfig && rawConfig.trim() !== '') {
|
||||||
|
try {
|
||||||
const configFile = `${TMP}/${confFile}`;
|
config = JSON.parse(rawConfig) as Record<string, unknown>;
|
||||||
|
} catch (error) {
|
||||||
const values: Record<string, unknown> = {};
|
Notify({
|
||||||
const defaultValues: Record<string, unknown> = {};
|
summary: 'Failed to load config file',
|
||||||
|
body: `${error}`,
|
||||||
for (let i = 0; i < allOptions.length; i++) {
|
iconName: icons.ui.warning,
|
||||||
const option = allOptions[i];
|
});
|
||||||
const val = option.value;
|
// Continue with a broken config, the user has
|
||||||
|
// been warned
|
||||||
values[option.id] = val;
|
|
||||||
|
|
||||||
if (isHexColor(val as string)) {
|
|
||||||
defaultValues[option.id] = option.initial;
|
|
||||||
} else {
|
|
||||||
defaultValues[option.id] = val;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
writeFile(defaultFile, JSON.stringify(defaultValues, null, 2));
|
// Initialize the config options
|
||||||
writeFile(configFile, JSON.stringify(values, null, 2));
|
const allOptions = getOptions(optionsObj as Record<string, unknown>);
|
||||||
|
for (let i = 0; i < allOptions.length; i++) {
|
||||||
|
allOptions[i].init(config);
|
||||||
|
}
|
||||||
|
|
||||||
monitorFile(configFile, () => {
|
// Setup a file monitor to allow live config edit preview from outside
|
||||||
const raw = readFile(configFile);
|
// the config menu
|
||||||
|
const debounceTimeMs = 200;
|
||||||
if (!raw || raw.trim() === '') return;
|
let lastEventTime = Date.now();
|
||||||
|
monitorFile(CONFIG, () => {
|
||||||
let cache: Record<string, unknown>;
|
if (Date.now() - lastEventTime < debounceTimeMs) {
|
||||||
|
|
||||||
try {
|
|
||||||
cache = JSON.parse(raw) as Record<string, unknown>;
|
|
||||||
} catch {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
lastEventTime = Date.now();
|
||||||
|
|
||||||
|
let newConfig: Record<string, unknown> = {};
|
||||||
|
|
||||||
|
const rawConfig = readFile(CONFIG);
|
||||||
|
if (rawConfig && rawConfig.trim() !== '') {
|
||||||
|
try {
|
||||||
|
newConfig = JSON.parse(rawConfig) as Record<string, unknown>;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error loading configuration file: ${error}`);
|
||||||
|
Notify({
|
||||||
|
summary: 'Loading configuration file failed',
|
||||||
|
body: `${error}`,
|
||||||
|
iconName: icons.ui.warning,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < allOptions.length; i++) {
|
for (let i = 0; i < allOptions.length; i++) {
|
||||||
const opt = allOptions[i];
|
const opt = allOptions[i];
|
||||||
const newVal = cache[opt.id];
|
const newVal = _findVal(newConfig, opt.id.split('.'));
|
||||||
const oldVal = opt.get();
|
|
||||||
|
|
||||||
if (JSON.stringify(newVal) !== JSON.stringify(oldVal)) {
|
if (newVal === undefined) {
|
||||||
opt.set(newVal as T);
|
// Set the variable but don't write it back to the file,
|
||||||
|
// as we are getting it from there
|
||||||
|
opt.reset({ writeDisk: false });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldVal = opt.get();
|
||||||
|
if (newVal !== oldVal) {
|
||||||
|
// Set the variable but don't write it back to the file,
|
||||||
|
// as we are getting it from there
|
||||||
|
opt.set(newVal, { writeDisk: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -325,8 +325,7 @@ export function mkOptions<T extends object>(
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.assign(object, {
|
return Object.assign(optionsObj, {
|
||||||
configFile,
|
|
||||||
array: (): Opt[] => allOptions,
|
array: (): Opt[] => allOptions,
|
||||||
async reset(): Promise<string> {
|
async reset(): Promise<string> {
|
||||||
const ids = await resetAll(allOptions);
|
const ids = await resetAll(allOptions);
|
||||||
|
|||||||
1
src/lib/types/options.d.ts
vendored
1
src/lib/types/options.d.ts
vendored
@@ -5,7 +5,6 @@ import { Astal } from 'astal/gtk3';
|
|||||||
import { dropdownMenuList } from '../constants/options';
|
import { dropdownMenuList } from '../constants/options';
|
||||||
|
|
||||||
export type MkOptionsResult = {
|
export type MkOptionsResult = {
|
||||||
configFile: string;
|
|
||||||
array: () => Opt[];
|
array: () => Opt[];
|
||||||
reset: () => Promise<string>;
|
reset: () => Promise<string>;
|
||||||
handler: (deps: string[], callback: () => void) => void;
|
handler: (deps: string[], callback: () => void) => void;
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ const tertiary_colors = {
|
|||||||
surface2: '#585b71',
|
surface2: '#585b71',
|
||||||
};
|
};
|
||||||
|
|
||||||
const options = mkOptions(CONFIG, {
|
const options = mkOptions({
|
||||||
theme: {
|
theme: {
|
||||||
tooltip: {
|
tooltip: {
|
||||||
scaling: opt(100),
|
scaling: opt(100),
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import options from '../options';
|
import options from '../options';
|
||||||
import { bash, dependencies } from '../lib/utils';
|
import { bash, dependencies } from '../lib/utils';
|
||||||
import { MatugenColors, RecursiveOptionsObject } from '../lib/types/options';
|
import { HexColor, MatugenColors, RecursiveOptionsObject } from '../lib/types/options';
|
||||||
import { initializeTrackers } from './optionsTrackers';
|
import { initializeTrackers } from './optionsTrackers';
|
||||||
import { generateMatugenColors, getMatugenHex, replaceHexValues } from '../services/matugen/index';
|
import { generateMatugenColors, getMatugenHex, replaceHexValues } from '../services/matugen/index';
|
||||||
import { isHexColor } from '../globals/variables';
|
import { isHexColor } from '../globals/variables';
|
||||||
import { readFile, writeFile } from 'astal/file';
|
import { readFile, writeFile } from 'astal/file';
|
||||||
import { App } from 'astal/gtk3';
|
import { App } from 'astal/gtk3';
|
||||||
import { initializeHotReload } from './utils/hotReload';
|
import { initializeHotReload } from './utils/hotReload';
|
||||||
import { defaultFile } from 'src/lib/option';
|
|
||||||
|
|
||||||
const deps = ['font', 'theme', 'bar.flatButtons', 'bar.position', 'bar.battery.charging', 'bar.battery.blocks'];
|
const deps = ['font', 'theme', 'bar.flatButtons', 'bar.position', 'bar.battery.charging', 'bar.battery.blocks'];
|
||||||
|
|
||||||
@@ -46,23 +45,26 @@ function extractVariables(theme: RecursiveOptionsObject, prefix = '', matugenCol
|
|||||||
async function extractMatugenizedVariables(matugenColors: MatugenColors): Promise<string[]> {
|
async function extractMatugenizedVariables(matugenColors: MatugenColors): Promise<string[]> {
|
||||||
try {
|
try {
|
||||||
const result = [] as string[];
|
const result = [] as string[];
|
||||||
|
const optArray = options.array();
|
||||||
|
|
||||||
const defaultFileContent = JSON.parse(readFile(defaultFile) || '{}');
|
for (let i = 0; i < optArray.length; i++) {
|
||||||
|
const opt = optArray[i];
|
||||||
|
const name = opt.id;
|
||||||
|
|
||||||
for (const key in defaultFileContent) {
|
if (name.startsWith('theme.') === false) {
|
||||||
if (key.startsWith('theme.') === false) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const configValue = defaultFileContent[key];
|
|
||||||
|
|
||||||
if (!isHexColor(configValue) && matugenColors !== undefined) {
|
|
||||||
result.push(`$${key.replace('theme.', '').split('.').join('-')}: ${configValue};`);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const matugenColor = getMatugenHex(configValue, matugenColors);
|
const initialValue = opt.initial;
|
||||||
|
|
||||||
result.push(`$${key.replace('theme.', '').split('.').join('-')}: ${matugenColor};`);
|
if (!isHexColor(initialValue) && matugenColors !== undefined) {
|
||||||
|
result.push(`$${name.replace('theme.', '').split('.').join('-')}: ${initialValue};`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const matugenColor = getMatugenHex(initialValue as HexColor, matugenColors);
|
||||||
|
|
||||||
|
result.push(`$${name.replace('theme.', '').split('.').join('-')}: ${matugenColor};`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
Reference in New Issue
Block a user