TeaWeb/shared/js/settings.ts

1075 lines
35 KiB
TypeScript
Raw Normal View History

2023-11-21 00:54:51 +00:00
import { LogCategory, logError, logInfo, logTrace } from "./log";
import * as loader from "tc-loader";
2023-11-21 00:54:51 +00:00
import { Stage } from "tc-loader";
import { Registry } from "./events";
import { tr } from "./i18n/localize";
import { CallOnce, ignorePromise } from "tc-shared/proto";
import { getStorageAdapter } from "tc-shared/StorageAdapter";
/*
* TODO: Sync settings across renderer instances
*/
2018-03-24 22:38:01 +00:00
2021-01-10 15:13:15 +00:00
export type RegistryValueType = boolean | number | string | object;
export type RegistryValueTypeNames = "boolean" | "number" | "string" | "object";
2020-08-09 12:30:17 +00:00
2021-01-10 15:13:15 +00:00
export type RegistryValueTypeMapping<T> = T extends boolean ? "boolean" :
2023-11-21 00:54:51 +00:00
T extends number ? "number" :
T extends string ? "string" :
T extends object ? "object" :
never;
2020-07-19 16:49:00 +00:00
2021-01-10 15:13:15 +00:00
export interface RegistryKey<ValueType extends RegistryValueType> {
2019-03-17 11:15:39 +00:00
key: string;
2021-01-10 15:13:15 +00:00
valueType: RegistryValueTypeMapping<ValueType>;
2020-07-19 16:49:00 +00:00
fallbackKeys?: string | string[];
2023-11-21 00:54:51 +00:00
fallbackImports?: { [key: string]: (value: string) => ValueType };
2019-03-17 11:15:39 +00:00
description?: string;
2019-08-21 08:00:01 +00:00
2020-07-19 16:49:00 +00:00
requireRestart?: boolean;
}
2021-01-10 15:13:15 +00:00
export interface ValuedRegistryKey<ValueType extends RegistryValueType> extends RegistryKey<ValueType> {
2020-07-19 16:49:00 +00:00
defaultValue: ValueType;
2019-03-17 11:15:39 +00:00
}
2021-01-10 15:13:15 +00:00
const UPDATE_DIRECT: boolean = true;
2018-02-27 16:20:49 +00:00
2023-11-21 00:54:51 +00:00
function decodeValueFromString<T extends RegistryValueType>(input: string, type: RegistryValueTypeMapping<T>): T {
2021-01-10 15:13:15 +00:00
switch (type) {
case "string":
return input as any;
2020-07-19 16:49:00 +00:00
2021-01-10 15:13:15 +00:00
case "boolean":
return (input === "1" || input === "true") as any;
2020-07-19 16:49:00 +00:00
2021-01-10 15:13:15 +00:00
case "number":
return parseFloat(input) as any;
2019-03-17 11:15:39 +00:00
2021-01-10 15:13:15 +00:00
case "object":
try {
return JSON.parse(input);
} catch (error) {
return {} as any;
}
2020-08-09 12:30:17 +00:00
2021-01-10 15:13:15 +00:00
default:
throw "value not decodable";
2018-04-19 17:46:47 +00:00
}
2021-01-10 15:13:15 +00:00
}
2020-07-19 16:49:00 +00:00
2023-11-21 00:54:51 +00:00
export function encodeSettingValueToString<T extends RegistryValueType>(input: T): string {
2021-01-10 15:13:15 +00:00
switch (typeof input) {
case "string":
return input;
2020-07-19 16:49:00 +00:00
2021-01-10 15:13:15 +00:00
case "boolean":
return input ? "1" : "0";
2018-02-27 16:20:49 +00:00
2021-01-10 15:13:15 +00:00
case "number":
return input.toString();
2020-07-19 16:49:00 +00:00
2021-01-10 15:13:15 +00:00
case "object":
return JSON.stringify(input);
2020-08-09 12:30:17 +00:00
2021-01-10 15:13:15 +00:00
default:
throw "value has invalid type";
2018-04-19 17:46:47 +00:00
}
2021-01-10 15:13:15 +00:00
}
2018-02-27 16:20:49 +00:00
export function resolveSettingKey<ValueType extends RegistryValueType, DefaultType>(
2021-01-10 15:13:15 +00:00
key: RegistryKey<ValueType>,
2021-02-19 22:05:48 +00:00
resolver: (key: string) => string | undefined | null,
2021-01-10 15:13:15 +00:00
defaultValue: DefaultType
2023-11-21 00:54:51 +00:00
): ValueType | DefaultType {
2021-01-10 15:13:15 +00:00
let value = resolver(key.key);
2021-02-19 22:05:48 +00:00
const keys = [key.key];
2023-11-21 00:54:51 +00:00
if (Array.isArray(key.fallbackKeys)) {
keys.push(...key.fallbackKeys);
2019-03-17 11:15:39 +00:00
}
2019-04-04 19:47:52 +00:00
2023-11-21 00:54:51 +00:00
for (const resolveKey of keys) {
value = resolver(resolveKey);
2023-11-21 00:54:51 +00:00
if (typeof value !== "string") {
2021-01-10 15:13:15 +00:00
continue;
}
2019-03-17 11:15:39 +00:00
switch (key.valueType) {
case "number":
case "boolean":
2023-11-21 00:54:51 +00:00
if (value.length === 0) {
continue;
}
break;
default:
break;
}
2023-11-21 00:54:51 +00:00
if (key.fallbackImports) {
const fallbackValueImporter = key.fallbackImports[resolveKey];
2023-11-21 00:54:51 +00:00
if (fallbackValueImporter) {
2021-02-14 16:22:53 +00:00
return fallbackValueImporter(value);
}
2018-04-19 17:46:47 +00:00
}
2021-01-10 15:13:15 +00:00
2021-02-14 16:22:53 +00:00
return decodeValueFromString(value, key.valueType);
2018-04-16 18:38:35 +00:00
}
2021-01-10 15:13:15 +00:00
return defaultValue;
}
2021-02-19 22:05:48 +00:00
export class UrlParameterParser {
private readonly url: URL;
constructor(url: URL) {
this.url = url;
}
2023-11-21 00:54:51 +00:00
private getParameter(key: string): string | undefined {
2021-02-19 22:05:48 +00:00
const value = this.url.searchParams.get(key);
2023-11-21 00:54:51 +00:00
if (value === null) {
2021-02-19 22:05:48 +00:00
return undefined;
}
return decodeURIComponent(value);
}
2021-01-10 15:13:15 +00:00
2023-11-21 00:54:51 +00:00
getValue<V extends RegistryValueType, DV>(key: RegistryKey<V>, defaultValue: DV): V | DV;
getValue<V extends RegistryValueType>(key: ValuedRegistryKey<V>, defaultValue?: V): V;
getValue<V extends RegistryValueType, DV>(key: RegistryKey<V> | ValuedRegistryKey<V>, defaultValue: DV): V | DV {
if (arguments.length > 1) {
return resolveSettingKey(key, key => this.getParameter(key), defaultValue);
2023-11-21 00:54:51 +00:00
} else if ("defaultValue" in key) {
return resolveSettingKey(key, key => this.getParameter(key), key.defaultValue);
2019-04-04 19:47:52 +00:00
} else {
2021-02-19 22:05:48 +00:00
throw tr("missing value");
2019-04-04 19:47:52 +00:00
}
2021-02-19 22:05:48 +00:00
}
}
2019-04-04 19:47:52 +00:00
2021-02-19 22:05:48 +00:00
export class UrlParameterBuilder {
private parameters = {};
setValue<V extends RegistryValueType>(key: RegistryKey<V>, value: V) {
2023-11-21 00:54:51 +00:00
if (value === undefined) {
2021-02-19 22:05:48 +00:00
delete this.parameters[key.key];
} else {
this.parameters[key.key] = encodeURIComponent(encodeSettingValueToString(value));
2021-02-19 22:05:48 +00:00
}
2018-04-16 18:38:35 +00:00
}
2023-11-21 00:54:51 +00:00
build(): string {
2021-02-19 22:05:48 +00:00
return Object.keys(this.parameters).map(key => `${key}=${this.parameters[key]}`).join("&");
}
}
/**
* Switched appended to the application via the URL.
* TODO: Passing native client switches
*/
export namespace AppParameters {
export const Instance = new UrlParameterParser(new URL(window.location.href));
2023-11-21 00:54:51 +00:00
export function getValue<V extends RegistryValueType, DV>(key: RegistryKey<V>, defaultValue: DV): V | DV;
export function getValue<V extends RegistryValueType>(key: ValuedRegistryKey<V>, defaultValue?: V): V;
export function getValue<V extends RegistryValueType, DV>(key: RegistryKey<V> | ValuedRegistryKey<V>, defaultValue: DV): V | DV {
if (arguments.length > 1) {
2021-02-19 22:05:48 +00:00
return Instance.getValue(key, defaultValue);
2023-11-21 00:54:51 +00:00
} else if ("defaultValue" in key) {
2021-02-19 22:05:48 +00:00
return Instance.getValue(key);
2021-01-10 15:13:15 +00:00
} else {
throw tr("missing value");
}
}
}
2021-02-19 22:05:48 +00:00
2021-01-16 12:36:06 +00:00
(window as any).AppParameters = AppParameters;
2018-02-27 16:20:49 +00:00
2021-01-10 15:13:15 +00:00
export namespace AppParameters {
export const KEY_CONNECT_ADDRESS: RegistryKey<string> = {
key: "ca",
fallbackKeys: ["connect_address"],
valueType: "string",
description: "A target address to automatically connect to."
};
export const KEY_CONNECT_INVITE_REFERENCE: RegistryKey<string> = {
key: "cir",
fallbackKeys: ["connect-invite-reference"],
valueType: "string",
description: "The invite link used to generate the connect parameters"
};
2021-01-10 15:13:15 +00:00
export const KEY_CONNECT_NO_SINGLE_INSTANCE: ValuedRegistryKey<boolean> = {
key: "cnsi",
fallbackKeys: ["connect_no_single_instance"],
defaultValue: false,
valueType: "boolean",
};
export const KEY_CONNECT_TYPE: ValuedRegistryKey<number> = {
key: "ct",
fallbackKeys: ["connect_type"],
valueType: "number",
defaultValue: 0,
description: "Connection establish type for automatic connect attempts.\n0 = Connect directly\n1 = Open connect modal"
};
export const KEY_CONNECT_NICKNAME: RegistryKey<string> = {
key: "cn",
2021-02-19 22:05:48 +00:00
fallbackKeys: ["connect_username", "nickname"],
2021-01-10 15:13:15 +00:00
valueType: "string"
};
export const KEY_CONNECT_TOKEN: RegistryKey<string> = {
key: "ctk",
2021-02-19 22:05:48 +00:00
fallbackKeys: ["connect_token", "connect-token", "token"],
2021-01-10 15:13:15 +00:00
valueType: "string",
description: "Token which will be used by default if the connection attempt succeeded."
};
export const KEY_CONNECT_PROFILE: RegistryKey<string> = {
key: "cp",
fallbackKeys: ["connect_profile"],
valueType: "string",
description: "The profile which should be used upon connect attempt."
};
export const KEY_CONNECT_SERVER_PASSWORD: RegistryKey<string> = {
key: "csp",
2021-02-19 22:05:48 +00:00
fallbackKeys: ["connect_server_password", "server-password"],
2021-01-10 15:13:15 +00:00
valueType: "string",
2021-02-19 22:05:48 +00:00
description: "The password for the auto connect attempt."
};
export const KEY_CONNECT_PASSWORDS_HASHED: ValuedRegistryKey<boolean> = {
key: "cph",
fallbackKeys: ["connect_passwords_hashed", "passwords-hashed"],
valueType: "boolean",
description: "Indicate whatever all passwords are hashed or not",
defaultValue: false
2021-01-10 15:13:15 +00:00
};
export const KEY_CONNECT_CHANNEL: RegistryKey<string> = {
key: "cc",
fallbackKeys: ["connect_channel"],
valueType: "string",
description: "The target channel for the connect attempt."
};
export const KEY_CONNECT_CHANNEL_PASSWORD: RegistryKey<string> = {
key: "ccp",
2021-02-19 22:05:48 +00:00
fallbackKeys: ["connect_channel_password", "channel-password"],
2021-01-10 15:13:15 +00:00
valueType: "string",
description: "The target channel password (hashed) for the connect attempt."
};
2021-02-20 16:46:17 +00:00
export const KEY_IPC_APP_ADDRESS: RegistryKey<string> = {
2021-01-10 15:13:15 +00:00
key: "ipc-address",
valueType: "string",
description: "Address of the apps IPC channel"
2021-01-10 15:13:15 +00:00
};
2021-02-20 16:46:17 +00:00
export const KEY_IPC_CORE_PEER_ADDRESS: RegistryKey<string> = {
key: "ipc-core-peer",
2021-01-16 12:36:06 +00:00
valueType: "string",
2021-02-20 16:46:17 +00:00
description: "Peer address of the apps core",
};
export const KEY_MODAL_IPC_CHANNEL: RegistryKey<string> = {
key: "modal-channel",
2021-02-20 16:46:17 +00:00
valueType: "string",
description: "The modal IPC channel id for communication with the controller"
2021-01-10 15:13:15 +00:00
};
export const KEY_LOAD_DUMMY_ERROR: ValuedRegistryKey<boolean> = {
key: "dummy_load_error",
description: "Triggers a loading error at the end of the loading process.",
valueType: "boolean",
defaultValue: false
};
}
export interface SettingsEvents {
notify_setting_changed: {
setting: string,
mode: "global" | "server",
oldValue: string,
2020-09-07 10:42:00 +00:00
newValue: string,
newCastedValue: any
}
}
2021-01-10 15:13:15 +00:00
export class Settings {
static readonly KEY_USER_IS_NEW: ValuedRegistryKey<boolean> = {
key: "user_is_new_user",
2020-07-19 16:49:00 +00:00
valueType: "boolean",
defaultValue: true
};
2021-01-10 15:13:15 +00:00
static readonly KEY_LOG_LEVEL: RegistryKey<number> = {
key: "log.level",
2020-07-19 16:49:00 +00:00
valueType: "number"
2020-03-27 15:15:15 +00:00
};
2021-01-10 15:13:15 +00:00
static readonly KEY_DISABLE_COSMETIC_SLOWDOWN: ValuedRegistryKey<boolean> = {
key: "disable_cosmetic_slowdown",
description: "Disable the cosmetic slowdows in some processes, like icon upload.",
2020-07-19 16:49:00 +00:00
valueType: "boolean",
defaultValue: false
2019-06-26 12:06:20 +00:00
};
2021-01-10 15:13:15 +00:00
static readonly KEY_DISABLE_CONTEXT_MENU: ValuedRegistryKey<boolean> = {
key: "disableContextMenu",
description: "Disable the context menu for the channel tree which allows to debug the DOM easier",
2020-07-19 16:49:00 +00:00
defaultValue: false,
valueType: "boolean",
2019-03-17 11:15:39 +00:00
};
2019-08-21 08:00:01 +00:00
2021-01-10 15:13:15 +00:00
static readonly KEY_DISABLE_GLOBAL_CONTEXT_MENU: ValuedRegistryKey<boolean> = {
key: "disableGlobalContextMenu",
description: "Disable the general context menu",
defaultValue: true,
2020-07-19 16:49:00 +00:00
valueType: "boolean",
2019-08-21 08:00:01 +00:00
};
2021-01-10 15:13:15 +00:00
static readonly KEY_DISABLE_UNLOAD_DIALOG: ValuedRegistryKey<boolean> = {
key: "disableUnloadDialog",
description: "Disables the unload popup on side closing",
2020-07-19 16:49:00 +00:00
valueType: "boolean",
defaultValue: false
2019-03-17 11:15:39 +00:00
};
2021-01-10 15:13:15 +00:00
static readonly KEY_DISABLE_VOICE: ValuedRegistryKey<boolean> = {
key: "disableVoice",
description: "Disables the voice bridge. If disabled, the audio and codec workers aren\'t required anymore",
2020-07-19 16:49:00 +00:00
valueType: "boolean",
defaultValue: false
2019-03-17 11:15:39 +00:00
};
2021-01-10 15:13:15 +00:00
static readonly KEY_DISABLE_MULTI_SESSION: ValuedRegistryKey<boolean> = {
key: "disableMultiSession",
2020-07-19 16:49:00 +00:00
defaultValue: false,
requireRestart: true,
valueType: "boolean",
2019-04-04 19:47:52 +00:00
};
2019-03-17 11:15:39 +00:00
2021-01-10 15:13:15 +00:00
static readonly KEY_I18N_DEFAULT_REPOSITORY: ValuedRegistryKey<string> = {
key: "i18n.default_repository",
2020-07-19 16:49:00 +00:00
valueType: "string",
2023-11-21 00:58:05 +00:00
defaultValue: "i18n/"
2019-03-25 19:04:04 +00:00
};
/* Default client states */
2021-01-10 15:13:15 +00:00
static readonly KEY_CLIENT_STATE_MICROPHONE_MUTED: ValuedRegistryKey<boolean> = {
key: "client_state_microphone_muted",
2020-07-19 16:49:00 +00:00
defaultValue: false,
fallbackKeys: ["mute_input"],
valueType: "boolean",
2019-03-17 11:15:39 +00:00
};
2020-07-19 16:49:00 +00:00
2021-01-10 15:13:15 +00:00
static readonly KEY_CLIENT_STATE_SPEAKER_MUTED: ValuedRegistryKey<boolean> = {
key: "client_state_speaker_muted",
2020-07-19 16:49:00 +00:00
defaultValue: false,
fallbackKeys: ["mute_output"],
valueType: "boolean",
2019-03-17 11:15:39 +00:00
};
2021-01-10 15:13:15 +00:00
static readonly KEY_CLIENT_STATE_QUERY_SHOWN: ValuedRegistryKey<boolean> = {
key: "client_state_query_shown",
2020-07-19 16:49:00 +00:00
defaultValue: false,
fallbackKeys: ["show_server_queries"],
valueType: "boolean",
2019-03-17 11:15:39 +00:00
};
2021-01-10 15:13:15 +00:00
static readonly KEY_CLIENT_STATE_SUBSCRIBE_ALL_CHANNELS: ValuedRegistryKey<boolean> = {
key: "client_state_subscribe_all_channels",
2020-07-19 16:49:00 +00:00
defaultValue: true,
fallbackKeys: ["channel_subscribe_all"],
valueType: "boolean",
};
2021-01-10 15:13:15 +00:00
static readonly KEY_CLIENT_STATE_AWAY: ValuedRegistryKey<boolean> = {
key: "client_state_away",
2020-07-19 16:49:00 +00:00
defaultValue: false,
valueType: "boolean",
};
2021-01-10 15:13:15 +00:00
static readonly KEY_CLIENT_AWAY_MESSAGE: ValuedRegistryKey<string> = {
key: "client_away_message",
2020-07-19 16:49:00 +00:00
defaultValue: "",
valueType: "string"
2019-03-17 11:15:39 +00:00
};
/* Connect parameters */
2021-01-10 15:13:15 +00:00
static readonly KEY_FLAG_CONNECT_DEFAULT: ValuedRegistryKey<boolean> = {
key: "connect_default",
2020-07-19 16:49:00 +00:00
valueType: "boolean",
2023-11-21 00:54:51 +00:00
defaultValue: true
2019-03-17 11:15:39 +00:00
};
2021-01-10 15:13:15 +00:00
static readonly KEY_CONNECT_ADDRESS: ValuedRegistryKey<string> = {
key: "connect_address",
2020-07-19 16:49:00 +00:00
valueType: "string",
2023-11-21 00:54:51 +00:00
defaultValue: "tea.lp.kle.li"
2019-03-17 11:15:39 +00:00
};
2021-01-10 15:13:15 +00:00
static readonly KEY_CONNECT_PROFILE: ValuedRegistryKey<string> = {
key: "connect_profile",
defaultValue: "default",
2020-07-19 16:49:00 +00:00
valueType: "string",
};
2021-01-10 15:13:15 +00:00
static readonly KEY_CONNECT_USERNAME: ValuedRegistryKey<string> = {
key: "connect_username",
2020-07-19 16:49:00 +00:00
valueType: "string",
defaultValue: undefined
};
2021-01-10 15:13:15 +00:00
static readonly KEY_CONNECT_PASSWORD: ValuedRegistryKey<string> = {
key: "connect_password",
2020-07-19 16:49:00 +00:00
valueType: "string",
defaultValue: undefined
};
2021-01-10 15:13:15 +00:00
static readonly KEY_FLAG_CONNECT_PASSWORD: ValuedRegistryKey<boolean> = {
key: "connect_password_hashed",
2020-07-19 16:49:00 +00:00
valueType: "boolean",
defaultValue: false
};
2021-01-10 15:13:15 +00:00
static readonly KEY_CONNECT_HISTORY: ValuedRegistryKey<string> = {
key: "connect_history",
2020-07-19 16:49:00 +00:00
valueType: "string",
defaultValue: ""
};
2021-01-10 15:13:15 +00:00
static readonly KEY_CONNECT_SHOW_HISTORY: ValuedRegistryKey<boolean> = {
key: "connect_show_last_servers",
2020-07-19 16:49:00 +00:00
valueType: "boolean",
defaultValue: false
};
2019-03-17 11:15:39 +00:00
2021-01-10 15:13:15 +00:00
static readonly KEY_CONNECT_NO_DNSPROXY: ValuedRegistryKey<boolean> = {
key: "connect_no_dnsproxy",
2023-11-21 00:54:51 +00:00
defaultValue: true,
2020-07-19 16:49:00 +00:00
valueType: "boolean",
2019-11-24 00:16:07 +00:00
};
2021-01-10 15:13:15 +00:00
static readonly KEY_CERTIFICATE_CALLBACK: ValuedRegistryKey<string> = {
key: "certificate_callback",
2020-07-19 16:49:00 +00:00
valueType: "string",
defaultValue: undefined
2019-04-04 19:47:52 +00:00
};
2019-04-29 16:49:01 +00:00
/* sounds */
2021-01-10 15:13:15 +00:00
static readonly KEY_SOUND_MASTER: ValuedRegistryKey<number> = {
key: "audio_master_volume",
2020-07-19 16:49:00 +00:00
defaultValue: 100,
valueType: "number",
2019-04-29 16:49:01 +00:00
};
2021-01-10 15:13:15 +00:00
static readonly KEY_SOUND_MASTER_SOUNDS: ValuedRegistryKey<number> = {
key: "audio_master_volume_sounds",
2020-07-19 16:49:00 +00:00
defaultValue: 100,
valueType: "number",
};
2021-01-10 15:13:15 +00:00
static readonly KEY_SOUND_VOLUMES: RegistryKey<string> = {
key: "sound_volume",
2020-07-19 16:49:00 +00:00
valueType: "string",
2019-08-21 08:00:01 +00:00
};
2021-01-10 15:13:15 +00:00
static readonly KEY_CHAT_FIXED_TIMESTAMPS: ValuedRegistryKey<boolean> = {
key: "chat_fixed_timestamps",
2020-07-19 16:49:00 +00:00
defaultValue: false,
2021-01-10 15:13:15 +00:00
description: "Enables fixed timestamps for chat messages and disabled the updating once (2 seconds ago... etc)",
2020-07-19 16:49:00 +00:00
valueType: "boolean",
2019-08-21 08:00:01 +00:00
};
2021-01-10 15:13:15 +00:00
static readonly KEY_CHAT_COLLOQUIAL_TIMESTAMPS: ValuedRegistryKey<boolean> = {
key: "chat_colloquial_timestamps",
2020-07-19 16:49:00 +00:00
defaultValue: true,
2021-01-10 15:13:15 +00:00
description: "Enabled colloquial timestamp formatting like \"Yesterday at ...\" or \"Today at ...\"",
2020-07-19 16:49:00 +00:00
valueType: "boolean",
2019-08-21 08:00:01 +00:00
};
2021-01-10 15:13:15 +00:00
static readonly KEY_CHAT_COLORED_EMOJIES: ValuedRegistryKey<boolean> = {
key: "chat_colored_emojies",
2020-07-19 16:49:00 +00:00
defaultValue: true,
2021-01-10 15:13:15 +00:00
description: "Enables colored emojies powered by Twemoji",
2020-07-19 16:49:00 +00:00
valueType: "boolean",
2019-08-21 08:00:01 +00:00
};
2021-01-10 15:13:15 +00:00
static readonly KEY_CHAT_HIGHLIGHT_CODE: ValuedRegistryKey<boolean> = {
key: "chat_highlight_code",
defaultValue: true,
2021-01-10 15:13:15 +00:00
description: "Enables code highlighting within the chat (Client restart required)",
valueType: "boolean",
};
2021-01-10 15:13:15 +00:00
static readonly KEY_CHAT_TAG_URLS: ValuedRegistryKey<boolean> = {
key: "chat_tag_urls",
2020-07-19 16:49:00 +00:00
defaultValue: true,
2021-01-10 15:13:15 +00:00
description: "Automatically link urls with [url]",
2020-07-19 16:49:00 +00:00
valueType: "boolean",
2019-08-21 08:00:01 +00:00
};
2021-01-10 15:13:15 +00:00
static readonly KEY_CHAT_ENABLE_MARKDOWN: ValuedRegistryKey<boolean> = {
key: "chat_enable_markdown",
2020-07-19 16:49:00 +00:00
defaultValue: true,
2021-01-10 15:13:15 +00:00
description: "Enabled markdown chat support.",
2020-07-19 16:49:00 +00:00
valueType: "boolean",
2019-08-21 08:00:01 +00:00
};
2021-01-10 15:13:15 +00:00
static readonly KEY_CHAT_ENABLE_BBCODE: ValuedRegistryKey<boolean> = {
key: "chat_enable_bbcode",
2020-07-19 16:49:00 +00:00
defaultValue: false,
2021-01-10 15:13:15 +00:00
description: "Enabled bbcode support in chat.",
2020-07-19 16:49:00 +00:00
valueType: "boolean",
2019-08-21 08:00:01 +00:00
};
2021-01-10 15:13:15 +00:00
static readonly KEY_CHAT_IMAGE_WHITELIST_REGEX: ValuedRegistryKey<string> = {
key: "chat_image_whitelist_regex",
2020-07-19 16:49:00 +00:00
defaultValue: JSON.stringify([]),
valueType: "string",
};
2021-02-15 18:03:22 +00:00
static readonly KEY_CHAT_LAST_USED_EMOJI: ValuedRegistryKey<string> = {
key: "chat_last_used_emoji",
defaultValue: ":joy:",
valueType: "string",
};
2021-01-10 15:13:15 +00:00
static readonly KEY_SWITCH_INSTANT_CHAT: ValuedRegistryKey<boolean> = {
key: "switch_instant_chat",
2020-07-19 16:49:00 +00:00
defaultValue: true,
2021-01-10 15:13:15 +00:00
description: "Directly switch to channel chat on channel select",
2020-07-19 16:49:00 +00:00
valueType: "boolean",
2019-08-21 08:00:01 +00:00
};
2021-01-10 15:13:15 +00:00
static readonly KEY_SWITCH_INSTANT_CLIENT: ValuedRegistryKey<boolean> = {
key: "switch_instant_client",
2020-07-19 16:49:00 +00:00
defaultValue: true,
2021-01-10 15:13:15 +00:00
description: "Directly switch to client info on client select",
2020-07-19 16:49:00 +00:00
valueType: "boolean",
2019-08-21 08:00:01 +00:00
};
2021-01-10 15:13:15 +00:00
static readonly KEY_HOSTBANNER_BACKGROUND: ValuedRegistryKey<boolean> = {
key: "hostbanner_background",
2020-07-19 16:49:00 +00:00
defaultValue: false,
2021-01-10 15:13:15 +00:00
description: "Enables a default background begind the hostbanner",
2020-07-19 16:49:00 +00:00
valueType: "boolean",
2019-08-21 08:00:01 +00:00
};
2021-01-10 15:13:15 +00:00
static readonly KEY_CHANNEL_EDIT_ADVANCED: ValuedRegistryKey<boolean> = {
key: "channel_edit_advanced",
2020-07-19 16:49:00 +00:00
defaultValue: false,
2021-01-10 15:13:15 +00:00
description: "Edit channels in advanced mode with a lot more settings",
2020-07-19 16:49:00 +00:00
valueType: "boolean",
2019-08-21 08:00:01 +00:00
};
2021-01-10 15:13:15 +00:00
static readonly KEY_PERMISSIONS_SHOW_ALL: ValuedRegistryKey<boolean> = {
key: "permissions_show_all",
2020-07-19 16:49:00 +00:00
defaultValue: false,
2021-01-10 15:13:15 +00:00
description: "Show all permissions even thou they dont make sense for the server/channel group",
2020-07-19 16:49:00 +00:00
valueType: "boolean",
};
2021-01-10 15:13:15 +00:00
static readonly KEY_TEAFORO_URL: ValuedRegistryKey<string> = {
2019-08-21 08:00:01 +00:00
key: "teaforo_url",
2020-07-19 16:49:00 +00:00
defaultValue: "https://forum.teaspeak.de/",
valueType: "string",
2019-08-21 08:00:01 +00:00
};
2021-01-10 15:13:15 +00:00
static readonly KEY_FONT_SIZE: ValuedRegistryKey<number> = {
2020-07-19 16:49:00 +00:00
key: "font_size",
valueType: "number",
2020-09-17 21:06:02 +00:00
defaultValue: 14 //parseInt(getComputedStyle(document.body).fontSize)
2019-04-29 16:49:01 +00:00
};
2019-10-19 15:13:40 +00:00
2021-01-10 15:13:15 +00:00
static readonly KEY_ICON_SIZE: ValuedRegistryKey<number> = {
2019-10-19 15:13:40 +00:00
key: "icon_size",
2020-07-19 16:49:00 +00:00
defaultValue: 100,
valueType: "number",
2019-10-19 15:13:40 +00:00
};
2019-04-29 16:49:01 +00:00
2021-01-10 15:13:15 +00:00
static readonly KEY_KEYCONTROL_DATA: ValuedRegistryKey<string> = {
2020-04-10 18:57:50 +00:00
key: "keycontrol_data",
2020-07-19 16:49:00 +00:00
defaultValue: "{}",
valueType: "string",
2020-04-10 18:57:50 +00:00
};
2021-01-10 15:13:15 +00:00
static readonly KEY_LAST_INVITE_LINK_TYPE: ValuedRegistryKey<string> = {
2019-10-13 19:33:07 +00:00
key: "last_invite_link_type",
2020-07-19 16:49:00 +00:00
defaultValue: "tea-web",
valueType: "string",
2019-10-13 19:33:07 +00:00
};
2021-01-10 15:13:15 +00:00
static readonly KEY_TRANSFERS_SHOW_FINISHED: ValuedRegistryKey<boolean> = {
key: "transfers_show_finished",
2020-07-19 16:49:00 +00:00
defaultValue: true,
description: "Show finished file transfers in the file transfer list",
valueType: "boolean",
};
2021-01-10 15:13:15 +00:00
static readonly KEY_TRANSFER_DOWNLOAD_FOLDER: RegistryKey<string> = {
key: "transfer_download_folder",
description: "The download folder for the file transfer downloads",
2020-07-19 16:49:00 +00:00
valueType: "string",
/* defaultValue: <users download directory> */
};
2023-11-21 00:54:51 +00:00
static readonly KEY_IPC_REMOTE_ADDRESS: RegistryKey<string> = {
key: "ipc-address",
valueType: "string"
};
2021-01-10 15:13:15 +00:00
static readonly KEY_W2G_SIDEBAR_COLLAPSED: ValuedRegistryKey<boolean> = {
key: "w2g_sidebar_collapsed",
defaultValue: false,
valueType: "boolean",
};
2021-01-10 15:13:15 +00:00
static readonly KEY_VOICE_ECHO_TEST_ENABLED: ValuedRegistryKey<boolean> = {
key: "voice_echo_test_enabled",
2020-09-07 10:42:00 +00:00
defaultValue: true,
valueType: "boolean",
};
static readonly KEY_RTC_EXTRA_VIDEO_CHANNELS: ValuedRegistryKey<number> = {
key: "rtc_extra_video_channels",
defaultValue: 0,
requireRestart: true,
valueType: "number",
description: "Extra video channels within the initial WebRTC sdp offer.\n" +
"Note: By default the screen/camera share channels are already present"
};
static readonly KEY_RTC_EXTRA_AUDIO_CHANNELS: ValuedRegistryKey<number> = {
key: "rtc_extra_audio_channels",
defaultValue: 6,
requireRestart: true,
valueType: "number",
description: "Extra audio channels within the initial WebRTC sdp offer.\n" +
"Note:\n" +
"1. By default the voice/whisper channels are already present.\n" +
"2. This setting does not work for Firefox."
};
2021-01-10 15:13:15 +00:00
static readonly KEY_RNNOISE_FILTER: ValuedRegistryKey<boolean> = {
key: "rnnoise_filter",
defaultValue: true,
description: "Enable the rnnoise filter for suppressing background noise",
valueType: "boolean",
};
2021-01-10 15:13:15 +00:00
static readonly KEY_LOADER_ANIMATION_ABORT: ValuedRegistryKey<boolean> = {
key: "loader_animation_abort",
defaultValue: false,
2021-01-10 15:13:15 +00:00
description: "Abort the loader animation when the app has been finished loading",
valueType: "boolean",
};
2021-01-10 15:13:15 +00:00
static readonly KEY_STOP_VIDEO_ON_SWITCH: ValuedRegistryKey<boolean> = {
key: "stop_video_on_channel_switch",
defaultValue: true,
2021-01-10 15:13:15 +00:00
description: "Stop video broadcasting on channel switch",
valueType: "boolean",
};
2021-01-10 15:13:15 +00:00
static readonly KEY_VIDEO_SHOW_ALL_CLIENTS: ValuedRegistryKey<boolean> = {
key: "video_show_all_clients",
defaultValue: false,
description: "Show all clients within the video frame, even if they're not broadcasting video",
valueType: "boolean",
};
2021-01-10 15:13:15 +00:00
static readonly KEY_VIDEO_FORCE_SHOW_OWN_VIDEO: ValuedRegistryKey<boolean> = {
key: "video_force_show_own_video",
defaultValue: true,
description: "Show own video preview even if you're not broadcasting any video",
valueType: "boolean",
};
2021-01-10 15:13:15 +00:00
static readonly KEY_VIDEO_AUTO_SUBSCRIBE_MODE: ValuedRegistryKey<number> = {
key: "video_auto_subscribe_mode",
defaultValue: 1,
description: "Auto subscribe to incoming videos.\n0 := Do not auto subscribe.\n1 := Auto subscribe to the first video.\n2 := Subscribe to all incoming videos.",
valueType: "number",
};
2021-01-10 15:13:15 +00:00
static readonly KEY_VIDEO_DEFAULT_MAX_WIDTH: ValuedRegistryKey<number> = {
key: "video_default_max_width",
defaultValue: 1280,
description: "The default maximal width of the video being crated.",
valueType: "number",
};
2021-01-10 15:13:15 +00:00
static readonly KEY_VIDEO_DEFAULT_MAX_HEIGHT: ValuedRegistryKey<number> = {
key: "video_default_max_height",
defaultValue: 720,
description: "The default maximal height of the video being crated.",
valueType: "number",
};
2021-01-15 23:03:01 +00:00
static readonly KEY_VIDEO_DEFAULT_MAX_BANDWIDTH: ValuedRegistryKey<number> = {
key: "video_default_max_bandwidth",
defaultValue: 1_600_000,
description: "The default video bandwidth to use in bits/seconds.\nA too high value might not be allowed by all server permissions.",
valueType: "number",
};
static readonly KEY_VIDEO_DEFAULT_KEYFRAME_INTERVAL: ValuedRegistryKey<number> = {
key: "video_default_keyframe_interval",
defaultValue: 0,
description: "The default interval to forcibly request a keyframe from ourself in seconds. A value of zero means no such interval.",
valueType: "number",
};
2021-01-10 15:13:15 +00:00
static readonly KEY_VIDEO_DYNAMIC_QUALITY: ValuedRegistryKey<boolean> = {
key: "video_dynamic_quality",
defaultValue: true,
description: "Dynamically decrease video quality in order to archive a higher framerate.",
valueType: "boolean",
};
2021-01-10 15:13:15 +00:00
static readonly KEY_VIDEO_DYNAMIC_FRAME_RATE: ValuedRegistryKey<boolean> = {
key: "video_dynamic_frame_rate",
defaultValue: true,
description: "Dynamically decrease video framerate to allow higher video resolutions.",
valueType: "boolean",
};
2021-01-10 15:13:15 +00:00
static readonly KEY_VIDEO_QUICK_SETUP: ValuedRegistryKey<boolean> = {
key: "video_quick_setup",
defaultValue: true,
description: "Automatically select the default video device and start broadcasting without the video configure dialog.",
valueType: "boolean",
};
2021-03-12 16:46:27 +00:00
static readonly KEY_VIDEO_SPOTLIGHT_MODE: ValuedRegistryKey<number> = {
key: "video_spotlight_mode",
defaultValue: 1,
description: "Select the video spotlight mode.\n0: Single video\n1: Video grid",
valueType: "number",
};
2021-02-19 22:05:48 +00:00
static readonly KEY_INVITE_SHORT_URL: ValuedRegistryKey<boolean> = {
key: "invite_short_url",
defaultValue: true,
description: "Enable/disable the short url for the invite menu",
valueType: "boolean",
};
static readonly KEY_INVITE_ADVANCED_ENABLED: ValuedRegistryKey<boolean> = {
key: "invite_advanced_enabled",
defaultValue: false,
description: "Enable/disable the advanced menu for the invite menu",
valueType: "boolean",
};
static readonly KEY_MICROPHONE_LEVEL_INDICATOR: RegistryKey<boolean> = {
key: "microphone_level_indicator",
description: "Enable/disable the microphone level indicator when opening the microphone settings. The default is true, except for the linux native client.",
valueType: "boolean",
};
static readonly KEY_MICROPHONE_THRESHOLD_ATTACK_SMOOTH: ValuedRegistryKey<number> = {
key: "microphone_threshold_attack_smooth",
valueType: "number",
defaultValue: .25
};
static readonly KEY_MICROPHONE_THRESHOLD_RELEASE_SMOOTH: ValuedRegistryKey<number> = {
key: "microphone_threshold_release_smooth",
valueType: "number",
defaultValue: .9
};
static readonly KEY_MICROPHONE_THRESHOLD_RELEASE_DELAY: ValuedRegistryKey<number> = {
key: "microphone_threshold_release_delay",
valueType: "number",
description: "Delay for the client to cut of the audio in ms.",
defaultValue: 500
};
static readonly KEY_SPEAKER_DEVICE_ID: RegistryKey<string> = {
key: "speaker_device_id",
valueType: "string",
description: "The target speaker device id",
}
static readonly KEY_UPDATER_LAST_USED_UI: RegistryKey<string> = {
key: "updater_last_used_ui",
valueType: "string",
description: "Last used TeaSpeak UI version",
}
static readonly KEY_UPDATER_LAST_USED_CLIENT: RegistryKey<string> = {
key: "updater_last_used_client",
valueType: "string",
description: "Last used TeaSpeak Client version (TeaClient only)",
}
/* When using a higher number clients crash due to a bug in NodeJS */
static readonly KEY_IPC_EVENT_BUNDLE_MAX_SIZE: ValuedRegistryKey<number> = {
key: "ipc_event_bundle_max_size",
valueType: "number",
defaultValue: 0
}
2021-01-10 15:13:15 +00:00
static readonly FN_LOG_ENABLED: (category: string) => RegistryKey<boolean> = category => {
2020-07-19 16:49:00 +00:00
return {
key: "log." + category.toLowerCase() + ".enabled",
valueType: "boolean",
}
};
2021-01-10 15:13:15 +00:00
static readonly FN_SEPARATOR_STATE: (separator: string) => RegistryKey<string> = separator => {
2020-07-19 16:49:00 +00:00
return {
key: "separator-settings-" + separator,
valueType: "string",
fallbackKeys: ["seperator-settings-" + separator]
}
};
2021-01-10 15:13:15 +00:00
static readonly FN_LOG_LEVEL_ENABLED: (category: string) => RegistryKey<boolean> = category => {
2020-07-19 16:49:00 +00:00
return {
key: "log.level." + category.toLowerCase() + ".enabled",
2020-08-09 12:30:17 +00:00
valueType: "boolean"
2020-07-19 16:49:00 +00:00
}
};
2021-01-10 15:13:15 +00:00
static readonly FN_INVITE_LINK_SETTING: (name: string) => RegistryKey<string> = name => {
2019-10-13 19:33:07 +00:00
return {
2021-01-10 15:13:15 +00:00
key: "invite_link_setting_" + name,
2020-07-19 16:49:00 +00:00
valueType: "string",
2019-10-13 19:33:07 +00:00
}
};
2021-01-10 15:13:15 +00:00
static readonly FN_SERVER_CHANNEL_SUBSCRIBE_MODE: (channel_id: number) => RegistryKey<number> = channel => {
2019-03-17 11:15:39 +00:00
return {
2021-01-10 15:13:15 +00:00
key: "channel_subscribe_mode_" + channel,
2020-07-19 16:49:00 +00:00
valueType: "number",
2019-03-17 11:15:39 +00:00
}
};
2021-01-10 15:13:15 +00:00
static readonly FN_SERVER_CHANNEL_COLLAPSED: (channel_id: number) => ValuedRegistryKey<boolean> = channel => {
2020-04-18 19:31:13 +00:00
return {
2021-01-10 15:13:15 +00:00
key: "channel_collapsed_" + channel,
2020-07-19 16:49:00 +00:00
defaultValue: false,
valueType: "boolean",
2020-04-18 19:31:13 +00:00
}
};
2021-01-10 15:13:15 +00:00
static readonly FN_PROFILE_RECORD: (name: string) => RegistryKey<object> = name => {
return {
2021-01-10 15:13:15 +00:00
key: "profile_record" + name,
2020-08-09 12:30:17 +00:00
valueType: "object",
}
};
2021-01-10 15:13:15 +00:00
static readonly FN_CHANNEL_CHAT_READ: (id: number) => RegistryKey<number> = id => {
return {
2021-01-10 15:13:15 +00:00
key: "channel_chat_read_" + id,
2020-07-19 16:49:00 +00:00
valueType: "number",
}
};
2021-01-10 15:13:15 +00:00
static readonly FN_CLIENT_MUTED: (clientUniqueId: string) => RegistryKey<boolean> = clientUniqueId => {
2020-07-19 16:49:00 +00:00
return {
key: "client_" + clientUniqueId + "_muted",
valueType: "boolean",
fallbackKeys: ["mute_client_" + clientUniqueId]
}
};
2021-01-10 15:13:15 +00:00
static readonly FN_CLIENT_VOLUME: (clientUniqueId: string) => RegistryKey<number> = clientUniqueId => {
2020-07-19 16:49:00 +00:00
return {
key: "client_" + clientUniqueId + "_volume",
valueType: "number",
fallbackKeys: ["volume_client_" + clientUniqueId]
}
};
2021-01-10 15:13:15 +00:00
static readonly FN_EVENTS_NOTIFICATION_ENABLED: (event: string) => RegistryKey<boolean> = event => {
return {
key: "event_notification_" + event + "_enabled",
valueType: "boolean"
}
};
2021-01-10 15:13:15 +00:00
static readonly FN_EVENTS_LOG_ENABLED: (event: string) => RegistryKey<boolean> = event => {
return {
key: "event_log_" + event + "_enabled",
valueType: "boolean"
}
};
2021-01-10 15:13:15 +00:00
static readonly FN_EVENTS_FOCUS_ENABLED: (event: string) => RegistryKey<boolean> = event => {
return {
key: "event_focus_" + event + "_enabled",
valueType: "boolean"
}
};
2019-03-17 11:15:39 +00:00
static readonly KEYS = (() => {
const result = [];
2023-11-21 00:54:51 +00:00
for (const key of Object.keys(Settings)) {
if (!key.toUpperCase().startsWith("KEY_")) {
2019-03-17 11:15:39 +00:00
continue;
2021-01-10 15:13:15 +00:00
}
2019-03-17 11:15:39 +00:00
result.push(key);
}
return result;
})();
2018-04-19 17:46:47 +00:00
readonly events: Registry<SettingsEvents>;
2021-01-10 15:13:15 +00:00
private settingsCache: any;
private saveWorker: number;
private updated: boolean;
private saveState: "none" | "saving" | "saving-changed";
2018-04-19 17:46:47 +00:00
constructor() {
this.events = new Registry<SettingsEvents>();
this.updated = false;
this.saveState = "none";
}
@CallOnce
async initialize() {
const json = await getStorageAdapter().get("settings.global");
try {
2023-11-21 00:54:51 +00:00
if (json === null) {
logInfo(LogCategory.GENERAL, tr("Found no settings. Creating new client settings."));
this.settingsCache = {};
} else {
this.settingsCache = JSON.parse(json);
}
2023-11-21 00:54:51 +00:00
} catch (error) {
this.settingsCache = {};
logError(LogCategory.GENERAL, tr("Failed to load global settings!\nJson: %s\nError: %o"), json, error);
const show_popup = () => {
//FIXME: Readd this
//createErrorModal(tr("Failed to load global settings"), tr("Failed to load global client settings!\nLookup console for more information.")).open();
};
2023-11-21 00:54:51 +00:00
if (!loader.finished()) {
loader.register_task(loader.Stage.LOADED, {
priority: 0,
name: "Settings error",
function: async () => show_popup()
});
} else {
show_popup();
}
}
2018-04-19 17:46:47 +00:00
this.saveWorker = setInterval(() => {
2023-11-21 00:54:51 +00:00
if (this.updated) {
2018-04-19 17:46:47 +00:00
this.save();
}
2018-04-19 17:46:47 +00:00
}, 5 * 1000);
2018-04-16 18:38:35 +00:00
}
2023-11-21 00:54:51 +00:00
getValue<V extends RegistryValueType, DV>(key: RegistryKey<V>, defaultValue: DV): V | DV;
getValue<V extends RegistryValueType>(key: ValuedRegistryKey<V>, defaultValue?: V): V;
getValue<V extends RegistryValueType, DV>(key: RegistryKey<V> | ValuedRegistryKey<V>, defaultValue: DV): V | DV {
if (arguments.length > 1) {
return resolveSettingKey(key, key => this.settingsCache[key], defaultValue);
2023-11-21 00:54:51 +00:00
} else if ("defaultValue" in key) {
return resolveSettingKey(key, key => this.settingsCache[key], key.defaultValue);
2021-01-10 15:13:15 +00:00
} else {
debugger;
2021-01-10 15:13:15 +00:00
throw tr("missing default value");
}
}
2023-11-21 00:54:51 +00:00
setValue<T extends RegistryValueType>(key: RegistryKey<T>, value?: T) {
if (value === null) {
2021-01-10 15:13:15 +00:00
value = undefined;
}
2018-02-27 16:20:49 +00:00
2023-11-21 00:54:51 +00:00
if (this.settingsCache[key.key] === value) {
2020-07-19 16:49:00 +00:00
return;
2021-01-10 15:13:15 +00:00
}
2018-03-07 18:06:52 +00:00
const oldValue = this.settingsCache[key.key];
2023-11-21 00:54:51 +00:00
if (value === undefined) {
delete this.settingsCache[key.key];
2021-01-10 15:13:15 +00:00
} else {
this.settingsCache[key.key] = encodeSettingValueToString(value);
2021-01-10 15:13:15 +00:00
}
this.updated = true;
this.events.fire("notify_setting_changed", {
mode: "global",
newValue: this.settingsCache[key.key],
oldValue: oldValue,
2020-09-07 10:42:00 +00:00
setting: key.key,
newCastedValue: value
});
logTrace(LogCategory.GENERAL, tr("Changing global setting %s to %o"), key.key, value);
2023-11-21 00:54:51 +00:00
if (UPDATE_DIRECT) {
2018-02-27 16:20:49 +00:00
this.save();
2021-01-10 15:13:15 +00:00
}
2018-02-27 16:20:49 +00:00
}
2023-11-21 00:54:51 +00:00
globalChangeListener<T extends RegistryValueType>(key: RegistryKey<T>, listener: (newValue: T) => void): () => void {
2020-09-07 10:42:00 +00:00
return this.events.on("notify_setting_changed", event => {
2023-11-21 00:54:51 +00:00
if (event.setting === key.key && event.mode === "global") {
2020-09-07 10:42:00 +00:00
listener(event.newCastedValue);
}
})
}
private async doSave() {
2023-11-21 00:54:51 +00:00
if (this.saveState === "none") {
2020-07-19 16:49:00 +00:00
return;
2021-01-10 15:13:15 +00:00
}
2018-03-07 18:06:52 +00:00
do {
this.saveState = "saving";
try {
await getStorageAdapter().set("settings.global", JSON.stringify(this.settingsCache));
} catch (error) {
logError(LogCategory.GENERAL, tr("Failed to save global settings: %o"), error);
}
2023-11-21 00:54:51 +00:00
} while (this.saveState !== "saving");
this.saveState = "none";
2018-02-27 16:20:49 +00:00
}
save() {
switch (this.saveState) {
case "saving":
case "saving-changed":
this.saveState = "saving-changed";
return;
2021-01-10 15:13:15 +00:00
default:
case "none":
this.saveState = "saving";
break;
2018-03-07 18:06:52 +00:00
}
ignorePromise(this.doSave());
2018-02-27 16:20:49 +00:00
}
2019-08-31 16:31:01 +00:00
}
export let settings: Settings;
loader.register_task(Stage.JAVASCRIPT_INITIALIZING, {
priority: 1100,
name: "Settings initialize",
function: async () => {
settings = new Settings();
await settings.initialize();
(window as any).settings = settings;
(window as any).Settings = Settings;
}
})