From 20ea54259861f7affd2501f4979d3f19130713f0 Mon Sep 17 00:00:00 2001 From: WolverinDEV Date: Sun, 10 Jan 2021 16:13:15 +0100 Subject: [PATCH] Reworked the settings module --- shared/js/ConnectionHandler.ts | 12 +- shared/js/KeyControl.ts | 4 +- shared/js/audio/recorder.ts | 2 +- shared/js/connection/rtc/video/Connection.ts | 2 +- shared/js/connectionlog/DispatcherFocus.ts | 2 +- .../connectionlog/DispatcherNotifications.ts | 2 +- shared/js/connectionlog/ServerEventLog.tsx | 2 +- .../ChannelConversationManager.ts | 4 +- shared/js/file/RemoteAvatars.ts | 6 +- shared/js/file/RemoteIcons.ts | 4 +- shared/js/log.ts | 6 +- shared/js/main.tsx | 283 +++---- .../js/profiles/identities/teaspeak-forum.ts | 2 +- shared/js/settings.ts | 766 ++++++++++-------- shared/js/sound/Sounds.ts | 4 +- shared/js/text/bbcode/emoji.tsx | 2 +- shared/js/text/bbcode/highlight.tsx | 6 +- shared/js/text/bbcode/image.tsx | 4 +- shared/js/text/bbcode/url.tsx | 4 +- shared/js/text/bbcode/youtube.tsx | 4 +- shared/js/text/chat.ts | 6 +- shared/js/tree/Channel.ts | 10 +- shared/js/tree/ChannelTree.tsx | 6 +- shared/js/tree/Client.ts | 14 +- shared/js/tree/Server.ts | 2 +- shared/js/ui/elements/ContextDivider.ts | 4 +- shared/js/ui/frames/chat.ts | 2 +- shared/js/ui/frames/control-bar/Controller.ts | 16 +- shared/js/ui/frames/control-bar/Renderer.tsx | 2 +- .../ui/frames/side/MusicPlaylistRenderer.tsx | 6 +- shared/js/ui/frames/video/Controller.ts | 4 +- shared/js/ui/modal/ModalBookmarks.ts | 2 +- shared/js/ui/modal/ModalChannelInfo.ts | 4 +- shared/js/ui/modal/ModalClientInfo.ts | 6 +- shared/js/ui/modal/ModalInvite.ts | 8 +- shared/js/ui/modal/ModalQueryManage.ts | 4 +- shared/js/ui/modal/ModalSettings.tsx | 54 +- shared/js/ui/modal/channel-edit/Renderer.tsx | 6 +- shared/js/ui/modal/connect/Controller.ts | 16 +- shared/js/ui/modal/echo-test/Controller.tsx | 4 +- .../global-settings-editor/Controller.tsx | 12 +- .../global-settings-editor/Definitions.ts | 4 +- .../permission/ModalPermissionEditor.tsx | 2 +- .../ui/modal/permission/PermissionEditor.tsx | 4 +- shared/js/ui/modal/permission/TabHandler.tsx | 4 +- shared/js/ui/modal/settings/Microphone.tsx | 4 +- shared/js/ui/modal/settings/Notifications.tsx | 8 +- .../transfer/FileTransferInfoController.ts | 4 +- .../js/ui/modal/video-source/Controller.tsx | 8 +- shared/js/ui/react-elements/ChatBox.tsx | 8 +- .../js/ui/react-elements/ContextDivider.tsx | 4 +- shared/js/ui/react-elements/Helper.ts | 10 +- .../external-modal/PopoutController.ts | 11 +- .../external-modal/PopoutEntrypoint.ts | 9 +- shared/js/ui/tree/EntryTags.tsx | 4 +- shared/js/ui/tree/RendererChannel.tsx | 2 +- shared/js/ui/tree/RendererClient.tsx | 2 +- shared/js/ui/tree/RendererServer.tsx | 2 +- shared/js/utils/DateUtils.ts | 4 +- shared/js/utils/helpers.ts | 27 +- shared/js/video-viewer/Controller.ts | 2 +- shared/js/video-viewer/Renderer.tsx | 4 +- shared/js/voice/RecorderProfile.ts | 4 +- web/app/UnloadHandler.ts | 3 +- web/app/connection/ServerConnection.ts | 2 +- 65 files changed, 725 insertions(+), 720 deletions(-) diff --git a/shared/js/ConnectionHandler.ts b/shared/js/ConnectionHandler.ts index eb5d9213..1b5ecb16 100644 --- a/shared/js/ConnectionHandler.ts +++ b/shared/js/ConnectionHandler.ts @@ -248,13 +248,13 @@ export class ConnectionHandler { } initialize_client_state(source?: ConnectionHandler) { - this.client_status.input_muted = source ? source.client_status.input_muted : settings.global(Settings.KEY_CLIENT_STATE_MICROPHONE_MUTED); - this.client_status.output_muted = source ? source.client_status.output_muted : settings.global(Settings.KEY_CLIENT_STATE_SPEAKER_MUTED); + this.client_status.input_muted = source ? source.client_status.input_muted : settings.getValue(Settings.KEY_CLIENT_STATE_MICROPHONE_MUTED); + this.client_status.output_muted = source ? source.client_status.output_muted : settings.getValue(Settings.KEY_CLIENT_STATE_SPEAKER_MUTED); this.update_voice_status(); - this.setSubscribeToAllChannels(source ? source.client_status.channel_subscribe_all : settings.global(Settings.KEY_CLIENT_STATE_SUBSCRIBE_ALL_CHANNELS)); - this.doSetAway(source ? source.client_status.away : (settings.global(Settings.KEY_CLIENT_STATE_AWAY) ? settings.global(Settings.KEY_CLIENT_AWAY_MESSAGE) : false), false); - this.setQueriesShown(source ? source.client_status.queries_visible : settings.global(Settings.KEY_CLIENT_STATE_QUERY_SHOWN)); + this.setSubscribeToAllChannels(source ? source.client_status.channel_subscribe_all : settings.getValue(Settings.KEY_CLIENT_STATE_SUBSCRIBE_ALL_CHANNELS)); + this.doSetAway(source ? source.client_status.away : (settings.getValue(Settings.KEY_CLIENT_STATE_AWAY) ? settings.getValue(Settings.KEY_CLIENT_AWAY_MESSAGE) : false), false); + this.setQueriesShown(source ? source.client_status.queries_visible : settings.getValue(Settings.KEY_CLIENT_STATE_QUERY_SHOWN)); } events() : Registry { @@ -457,7 +457,7 @@ export class ConnectionHandler { control_bar.apply_server_voice_state(); */ - if(__build.target === "web" && settings.static_global(Settings.KEY_VOICE_ECHO_TEST_ENABLED)) { + if(__build.target === "web" && settings.getValue(Settings.KEY_VOICE_ECHO_TEST_ENABLED)) { this.serverFeatures.awaitFeatures().then(result => { if(!result) { return; diff --git a/shared/js/KeyControl.ts b/shared/js/KeyControl.ts index 2c0afad2..ea1c4e47 100644 --- a/shared/js/KeyControl.ts +++ b/shared/js/KeyControl.ts @@ -135,7 +135,7 @@ let config: Config; export function initialize() { let cfg: Config; try { - cfg = JSON.parse(settings.global(Settings.KEY_KEYCONTROL_DATA)); + cfg = JSON.parse(settings.getValue(Settings.KEY_KEYCONTROL_DATA)); } catch (e) { log.error(LogCategory.GENERAL, tr("Failed to parse old key control data.")); cfg = {}; @@ -163,7 +163,7 @@ export function initialize() { } function save_config() { - settings.changeGlobal(Settings.KEY_KEYCONTROL_DATA, JSON.stringify(config)); + settings.setValue(Settings.KEY_KEYCONTROL_DATA, JSON.stringify(config)); } function bind_key(action: string, key: KeyDescriptor) { diff --git a/shared/js/audio/recorder.ts b/shared/js/audio/recorder.ts index 9309df10..8f5b3147 100644 --- a/shared/js/audio/recorder.ts +++ b/shared/js/audio/recorder.ts @@ -175,7 +175,7 @@ loader.register_task(Stage.JAVASCRIPT_INITIALIZING, { function: async () => { const backend = getRecorderBackend(); if(backend.isRnNoiseSupported()) { - getRecorderBackend().toggleRnNoise(settings.static_global(Settings.KEY_RNNOISE_FILTER)); + getRecorderBackend().toggleRnNoise(settings.getValue(Settings.KEY_RNNOISE_FILTER)); settings.globalChangeListener(Settings.KEY_RNNOISE_FILTER, value => getRecorderBackend().toggleRnNoise(value)); } } diff --git a/shared/js/connection/rtc/video/Connection.ts b/shared/js/connection/rtc/video/Connection.ts index 16015253..261239ea 100644 --- a/shared/js/connection/rtc/video/Connection.ts +++ b/shared/js/connection/rtc/video/Connection.ts @@ -365,7 +365,7 @@ export class RtpVideoConnection implements VideoConnection { client.setBroadcastId("camera", undefined); }); - if(settings.static_global(Settings.KEY_STOP_VIDEO_ON_SWITCH)) { + if(settings.getValue(Settings.KEY_STOP_VIDEO_ON_SWITCH)) { Object.values(this.broadcasts).forEach(broadcast => broadcast.stopBroadcasting()); } else { /* The server stops broadcasting by default, we've to reenable it */ diff --git a/shared/js/connectionlog/DispatcherFocus.ts b/shared/js/connectionlog/DispatcherFocus.ts index afd72258..b3ef7de4 100644 --- a/shared/js/connectionlog/DispatcherFocus.ts +++ b/shared/js/connectionlog/DispatcherFocus.ts @@ -15,5 +15,5 @@ export function requestWindowFocus() { } export function isFocusRequestEnabled(type: EventType) { - return settings.global(Settings.FN_EVENTS_FOCUS_ENABLED(type), focusDefaultStatus[type as any] || false); + return settings.getValue(Settings.FN_EVENTS_FOCUS_ENABLED(type), focusDefaultStatus[type as any] || false); } \ No newline at end of file diff --git a/shared/js/connectionlog/DispatcherNotifications.ts b/shared/js/connectionlog/DispatcherNotifications.ts index b7ef40f8..a9b5f396 100644 --- a/shared/js/connectionlog/DispatcherNotifications.ts +++ b/shared/js/connectionlog/DispatcherNotifications.ts @@ -42,7 +42,7 @@ export function getRegisteredNotificationDispatchers() : TypeInfo[] { } export function isNotificationEnabled(type: EventType) { - return settings.global(Settings.FN_EVENTS_NOTIFICATION_ENABLED(type), notificationDefaultStatus[type as any] || false); + return settings.getValue(Settings.FN_EVENTS_NOTIFICATION_ENABLED(type), notificationDefaultStatus[type as any] || false); } const kDefaultIcon = "img/teaspeak_cup_animated.png"; diff --git a/shared/js/connectionlog/ServerEventLog.tsx b/shared/js/connectionlog/ServerEventLog.tsx index bd32c2be..14f1425b 100644 --- a/shared/js/connectionlog/ServerEventLog.tsx +++ b/shared/js/connectionlog/ServerEventLog.tsx @@ -31,7 +31,7 @@ export class ServerEventLog { uniqueId: "log-" + Date.now() + "-" + (++uniqueLogEventId) }; - if(settings.global(Settings.FN_EVENTS_LOG_ENABLED(type), true)) { + if(settings.getValue(Settings.FN_EVENTS_LOG_ENABLED(type), true)) { this.eventLog.push(event); while(this.eventLog.length > this.maxHistoryLength) { this.eventLog.pop_front(); diff --git a/shared/js/conversations/ChannelConversationManager.ts b/shared/js/conversations/ChannelConversationManager.ts index 75710446..3ad71554 100644 --- a/shared/js/conversations/ChannelConversationManager.ts +++ b/shared/js/conversations/ChannelConversationManager.ts @@ -42,7 +42,7 @@ export class ChannelConversation extends AbstractChat this.conversationId = id; this.preventUnreadUpdate = true; - const unreadTimestamp = handle.connection.settings.server(Settings.FN_CHANNEL_CHAT_READ(id), Date.now()); + const unreadTimestamp = handle.connection.settings.getValue(Settings.FN_CHANNEL_CHAT_READ(id), Date.now()); this.setUnreadTimestamp(unreadTimestamp); this.preventUnreadUpdate = false; @@ -292,7 +292,7 @@ export class ChannelConversation extends AbstractChat return; } - this.handle.connection.settings.changeServer(Settings.FN_CHANNEL_CHAT_READ(this.conversationId), timestamp); + this.handle.connection.settings.setValue(Settings.FN_CHANNEL_CHAT_READ(this.conversationId), timestamp); } public setConversationMode(mode: ChannelConversationMode, logChange: boolean) { diff --git a/shared/js/file/RemoteAvatars.ts b/shared/js/file/RemoteAvatars.ts index ba11bd3f..e0f5b469 100644 --- a/shared/js/file/RemoteAvatars.ts +++ b/shared/js/file/RemoteAvatars.ts @@ -9,7 +9,7 @@ import { setGlobalAvatarManagerFactory, uniqueId2AvatarId } from "../file/Avatars"; import {IPCChannel} from "../ipc/BrowserIPC"; -import {Settings} from "../settings"; +import {AppParameters} from "../settings"; import {ChannelMessage} from "../ipc/BrowserIPC"; import {guid} from "../crypto/uid"; import { tr } from "tc-shared/i18n/localize"; @@ -159,7 +159,7 @@ class RemoteAvatarManagerFactory extends AbstractAvatarManagerFactory { constructor() { super(); - this.ipcChannel = ipc.getIpcInstance().createChannel(Settings.instance.static(Settings.KEY_IPC_REMOTE_ADDRESS, "invalid"), kIPCAvatarChannel); + this.ipcChannel = ipc.getIpcInstance().createChannel(AppParameters.getValue(AppParameters.KEY_IPC_REMOTE_ADDRESS, "invalid"), kIPCAvatarChannel); this.ipcChannel.messageHandler = this.handleIpcMessage.bind(this); } @@ -187,7 +187,7 @@ class RemoteAvatarManagerFactory extends AbstractAvatarManagerFactory { return typeof this.manager[handlerId] !== "undefined"; } - private handleIpcMessage(remoteId: string, broadcast: boolean, message: ChannelMessage) { + private handleIpcMessage(_remoteId: string, broadcast: boolean, message: ChannelMessage) { if(broadcast) { if(message.type === "notify-handler-destroyed") { const manager = this.manager[message.data.handler]; diff --git a/shared/js/file/RemoteIcons.ts b/shared/js/file/RemoteIcons.ts index 3550b10b..7aef5860 100644 --- a/shared/js/file/RemoteIcons.ts +++ b/shared/js/file/RemoteIcons.ts @@ -3,7 +3,7 @@ import * as loader from "tc-loader"; import {Stage} from "tc-loader"; import {ChannelMessage, IPCChannel} from "tc-shared/ipc/BrowserIPC"; import * as ipc from "tc-shared/ipc/BrowserIPC"; -import {Settings} from "tc-shared/settings"; +import {AppParameters} from "tc-shared/settings"; import {LogCategory, logWarn} from "tc-shared/log"; class RemoteRemoteIcon extends RemoteIcon { @@ -33,7 +33,7 @@ class RemoteIconManager extends AbstractIconManager { constructor() { super(); - this.ipcChannel = ipc.getIpcInstance().createChannel(Settings.instance.static(Settings.KEY_IPC_REMOTE_ADDRESS, "invalid"), kIPCIconChannel); + this.ipcChannel = ipc.getIpcInstance().createChannel(AppParameters.getValue(AppParameters.KEY_IPC_REMOTE_ADDRESS, "invalid"), kIPCIconChannel); this.ipcChannel.messageHandler = this.handleIpcMessage.bind(this); } diff --git a/shared/js/log.ts b/shared/js/log.ts index 92e45e1a..4406e6d8 100644 --- a/shared/js/log.ts +++ b/shared/js/log.ts @@ -100,13 +100,13 @@ const defaultGroupMode: GroupMode = GroupMode.PREFIX; export function initialize(defaultLogLevel: LogType) { for(const category of Object.keys(LogCategory).map(e => parseInt(e)).filter(e => !isNaN(e))) { const categoryName = LogCategory[category].toLowerCase(); - enabled_mapping.set(category, settings.static_global(Settings.FN_LOG_ENABLED(categoryName), enabled_mapping.get(category))); + enabled_mapping.set(category, settings.getValue(Settings.FN_LOG_ENABLED(categoryName), enabled_mapping.get(category))); } - const baseLogLevel = settings.static_global(Settings.KEY_LOG_LEVEL, defaultLogLevel); + const baseLogLevel = settings.getValue(Settings.KEY_LOG_LEVEL, defaultLogLevel); for(const level of Object.keys(LogType).map(e => parseInt(e)).filter(e => !isNaN(e))) { const levelName = LogType[level].toLowerCase(); - level_mapping.set(level, settings.static_global(Settings.FN_LOG_LEVEL_ENABLED(levelName), level >= baseLogLevel)); + level_mapping.set(level, settings.getValue(Settings.FN_LOG_LEVEL_ENABLED(levelName), level >= baseLogLevel)); } } diff --git a/shared/js/main.tsx b/shared/js/main.tsx index 431683b2..806c98e5 100644 --- a/shared/js/main.tsx +++ b/shared/js/main.tsx @@ -1,6 +1,6 @@ import * as loader from "tc-loader"; import {Stage} from "tc-loader"; -import {settings, Settings} from "tc-shared/settings"; +import {AppParameters, settings, Settings} from "tc-shared/settings"; import * as log from "tc-shared/log"; import {LogCategory} from "tc-shared/log"; import * as bipc from "./ipc/BrowserIPC"; @@ -23,7 +23,7 @@ import * as global_ev_handler from "./events/ClientGlobalControlHandler"; import {global_client_actions} from "tc-shared/events/GlobalEvents"; import {FileTransferState, TransferProvider,} from "tc-shared/file/Transfer"; import {MenuEntryType, spawn_context_menu} from "tc-shared/ui/elements/ContextMenu"; -import {copy_to_clipboard} from "tc-shared/utils/helpers"; +import {copyToClipboard} from "tc-shared/utils/helpers"; import {checkForUpdatedApp} from "tc-shared/update"; import {setupJSRender} from "tc-shared/ui/jsrender"; import {ConnectRequestData} from "tc-shared/ipc/ConnectHandler"; @@ -81,12 +81,7 @@ async function initialize_app() { console.warn(tr("Failed to initialize audio controller!")); } - aplayer.on_ready(() => { - if(aplayer.set_master_volume) - aplayer.on_ready(() => aplayer.set_master_volume(settings.global(Settings.KEY_SOUND_MASTER) / 100)); - else - log.warn(LogCategory.GENERAL, tr("Client does not support aplayer.set_master_volume()... May client is too old?")); - }); + aplayer.on_ready(() => aplayer.set_master_volume(settings.getValue(Settings.KEY_SOUND_MASTER) / 100)); setDefaultRecorder(new RecorderProfile("default")); defaultRecorder.initialize().catch(error => { @@ -96,7 +91,7 @@ async function initialize_app() { sound.initialize().then(() => { log.info(LogCategory.AUDIO, tr("Sounds initialized")); }); - sound.set_master_volume(settings.global(Settings.KEY_SOUND_MASTER_SOUNDS) / 100); + sound.set_master_volume(settings.getValue(Settings.KEY_SOUND_MASTER_SOUNDS) / 100); try { await ppt.initialize(); @@ -116,7 +111,7 @@ export function handle_connect_request(properties: ConnectRequestData, connectio const password_hashed = properties.password ? properties.password.hashed : false; if(profile && profile.valid()) { - settings.changeGlobal(Settings.KEY_USER_IS_NEW, false); + settings.setValue(Settings.KEY_USER_IS_NEW, false); if(!aplayer.initialized()) { spawnYesNo(tra("Connect to {}", properties.address), tra("Would you like to connect to {}?", properties.address), result => { @@ -147,7 +142,7 @@ export function handle_connect_request(properties: ConnectRequestData, connectio function main() { /* initialize font */ { - const font = settings.static_global(Settings.KEY_FONT_SIZE); + const font = settings.getValue(Settings.KEY_FONT_SIZE); $(document.body).css("font-size", font + "px"); settings.globalChangeListener(Settings.KEY_FONT_SIZE, value => { $(document.body).css("font-size", value + "px"); @@ -165,9 +160,7 @@ function main() { spawn_context_menu(event.pageX, event.pageY, { type: MenuEntryType.ENTRY, name: tr("Copy"), - callback: () => { - copy_to_clipboard(event.target.value); - }, + callback: () => copyToClipboard(event.target.value), icon_class: "client-copy", visible: !!event.target.value }, { @@ -181,21 +174,20 @@ function main() { visible: __build.target === "client", }); } + event.preventDefault(); return; } - if(settings.static_global(Settings.KEY_DISABLE_GLOBAL_CONTEXT_MENU)) { + if(settings.getValue(Settings.KEY_DISABLE_GLOBAL_CONTEXT_MENU)) { event.preventDefault(); } }); window.removeLoaderContextMenuHook(); - //top_menu.initialize(); - - const initial_handler = server_connections.spawn_server_connection(); - initial_handler.acquireInputHardware().then(() => {}); - server_connections.set_active_connection(initial_handler); + const initialHandler = server_connections.spawn_server_connection(); + server_connections.set_active_connection(initialHandler); + initialHandler.acquireInputHardware().then(() => {}); /** Setup the XF forum identity **/ fidentity.update_forum(); @@ -206,113 +198,17 @@ function main() { anonymize_ip_addresses: true, volatile_collection_only: false }); + stats.register_user_count_listener(status => { log.info(LogCategory.STATISTICS, tr("Received user count update: %o"), status); }); - server_connections.set_active_connection(server_connections.all_connections()[0]); checkForUpdatedApp(); - (window as any).test_download = async () => { - const connection = server_connections.active_connection(); - const download = connection.fileManager.initializeFileDownload({ - targetSupplier: async () => await TransferProvider.provider().createDownloadTarget(), - name: "HomeStudent2019Retail.img", - path: "/", - channel: 4 - }); - - console.log("Download stated"); - await download.awaitFinished(); - console.log("Download finished (%s)", FileTransferState[download.transferState()]); - //console.log(await (download.target as ResponseTransferTarget).getResponse().blob()); - console.log("Have buffer"); - }; - - (window as any).test_upload = async () => { - const connection = server_connections.active_connection(); - const download = connection.fileManager.initializeFileUpload({ - source: async () => await TransferProvider.provider().createTextSource("Hello my lovely world...."), - name: "test-upload.txt", - path: "/", - channel: 4 - }); - - console.log("Download stated"); - await download.awaitFinished(); - console.log("Download finished (%s)", FileTransferState[download.transferState()]); - //console.log(await (download.target as ResponseTransferTarget).getResponse().blob()); - console.log("Have buffer"); - }; - - /* schedule it a bit later then the main because the main function is still within the loader */ - setTimeout(() => { - //(window as any).spawnVideo(); - /* - Modals.createChannelModal(connection, undefined, undefined, connection.permissions, (cb, perms) => { - - }); - */ - // Modals.openServerInfo(connection.channelTree.server); - //Modals.createServerModal(connection.channelTree.server, properties => Promise.resolve()); - - //Modals.openClientInfo(connection.getClient()); - //Modals.openServerInfoBandwidth(connection.channelTree.server); - - //Modals.openBanList(connection); - /* - Modals.spawnBanClient(connection,[ - {name: "WolverinDEV", unique_id: "XXXX"}, - {name: "WolverinDEV", unique_id: "XXXX"}, - {name: "WolverinDEV", unique_id: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"}, - {name: "WolverinDEV", unique_id: "YYY"} - ], () => {}); - */ - }, 4000); - //spawnSettingsModal("general-keymap"); - //Modals.spawnKeySelect(console.log); - //Modals.spawnBookmarkModal(); - - /* - { - const modal = createModal({ - header: tr("Test Net Graph"), - body: () => { - const canvas = $.spawn("canvas") - .css("position", "absolute") - .css({ - top: 0, - bottom: 0, - right: 0, - left: 0 - }); - - return $.spawn("div") - .css("height", "5em") - .css("width", "30em") - .css("position", "relative") - .append(canvas); - }, - footer: null - }); - - const graph = new net.graph.Graph(modal.htmlTag.find("canvas")[0] as any); - graph.initialize(); - - modal.close_listener.push(() => graph.terminate()); - modal.open(); - } - */ - //setTimeout(() => spawnPermissionEditorModal(server_connections.active_connection()), 3000); - //setTimeout(() => spawnGroupCreate(server_connections.active_connection(), "server"), 3000); - - if(settings.static_global(Settings.KEY_USER_IS_NEW) && !preventWelcomeUI) { + if(settings.getValue(Settings.KEY_USER_IS_NEW) && !preventWelcomeUI) { const modal = openModalNewcomer(); - modal.close_listener.push(() => settings.changeGlobal(Settings.KEY_USER_IS_NEW, false)); + modal.close_listener.push(() => settings.setValue(Settings.KEY_USER_IS_NEW, false)); } - - //spawnGlobalSettingsEditor(); - //spawnVideoPopout(server_connections.active_connection(), "https://www.youtube.com/watch?v=9683D18fyvs"); } const task_teaweb_starter: loader.Task = { @@ -325,14 +221,17 @@ const task_teaweb_starter: loader.Task = { log.info(LogCategory.VOICE, tr("Initialize audio controller later!")); if(!aplayer.initializeFromGesture) { console.error(tr("Missing aplayer.initializeFromGesture")); - } else + } else { $(document).one('click', () => aplayer.initializeFromGesture()); + } } - loader.config.abortAnimationOnFinish = settings.static_global(Settings.KEY_LOADER_ANIMATION_ABORT); + + loader.config.abortAnimationOnFinish = settings.getValue(Settings.KEY_LOADER_ANIMATION_ABORT); } catch (ex) { console.error(ex.stack); - if(ex instanceof ReferenceError || ex instanceof TypeError) + if(ex instanceof ReferenceError || ex instanceof TypeError) { ex = ex.name + ": " + ex.message; + } loader.critical_error("Failed to invoke main function:
" + ex); } }, @@ -342,81 +241,78 @@ const task_teaweb_starter: loader.Task = { const task_connect_handler: loader.Task = { name: "Connect handler", function: async () => { - const address = settings.static(Settings.KEY_CONNECT_ADDRESS, ""); + const address = AppParameters.getValue(AppParameters.KEY_CONNECT_ADDRESS, undefined); + if(typeof address === "undefined") { + loader.register_task(loader.Stage.LOADED, task_teaweb_starter); + return; + } + + /* FIXME: All additional parameters! */ + const connectData = { + address: address, + + profile: AppParameters.getValue(AppParameters.KEY_CONNECT_PROFILE, ""), + username: AppParameters.getValue(AppParameters.KEY_CONNECT_NICKNAME, ""), + + password: { + value: AppParameters.getValue(AppParameters.KEY_CONNECT_SERVER_PASSWORD, ""), + hashed: true + } + }; + const chandler = bipc.getInstanceConnectHandler(); - if(settings.static(Settings.KEY_FLAG_CONNECT_DEFAULT, false) && address) { - const connect_data = { - address: address, + if(chandler && AppParameters.getValue(AppParameters.KEY_CONNECT_NO_SINGLE_INSTANCE)) { + try { + await chandler.post_connect_request(connectData, () => new Promise(resolve => { + spawnYesNo(tr("Another TeaWeb instance is already running"), tra("Another TeaWeb instance is already running.{:br:}Would you like to connect there?"), response => { + resolve(response); + }, { + closeable: false + }).open(); + })); + log.info(LogCategory.CLIENT, tr("Executed connect successfully in another browser window. Closing this window")); - profile: settings.static(Settings.KEY_CONNECT_PROFILE, ""), - username: settings.static(Settings.KEY_CONNECT_USERNAME, ""), - - password: { - value: settings.static(Settings.KEY_CONNECT_PASSWORD, ""), - hashed: settings.static(Settings.KEY_FLAG_CONNECT_PASSWORD, false) - } - }; - - if(chandler && !settings.static(Settings.KEY_CONNECT_NO_SINGLE_INSTANCE)) { - try { - await chandler.post_connect_request(connect_data, () => new Promise(resolve => { - spawnYesNo(tr("Another TeaWeb instance is already running"), tra("Another TeaWeb instance is already running.{:br:}Would you like to connect there?"), response => { - resolve(response); - }, { - closeable: false - }).open(); - })); - log.info(LogCategory.CLIENT, tr("Executed connect successfully in another browser window. Closing this window")); - - const message = - "You're connecting to {0} within the other TeaWeb instance.{:br:}" + - "You could now close this page."; - createInfoModal( - tr("Connecting successfully within other instance"), - formatMessage(/* @tr-ignore */ tr(message), connect_data.address), - { - closeable: false, - footer: undefined - } - ).open(); - return; - } catch(error) { - log.info(LogCategory.CLIENT, tr("Failed to execute connect within other TeaWeb instance. Using this one. Error: %o"), error); - } + const message = + "You're connecting to {0} within the other TeaWeb instance.{:br:}" + + "You could now close this page."; + createInfoModal( + tr("Connecting successfully within other instance"), + formatMessage(/* @tr-ignore */ tr(message), connectData.address), + { + closeable: false, + footer: undefined + } + ).open(); + return; + } catch(error) { + log.info(LogCategory.CLIENT, tr("Failed to execute connect within other TeaWeb instance. Using this one. Error: %o"), error); } - preventWelcomeUI = true; - loader.register_task(loader.Stage.LOADED, { - priority: 0, - function: async () => handle_connect_request(connect_data, server_connections.active_connection() || server_connections.spawn_server_connection()), - name: tr("default url connect") - }); - } - if(chandler) { - /* no instance avail, so lets make us avail */ - chandler.callback_available = data => { - return !settings.static_global(Settings.KEY_DISABLE_MULTI_SESSION); - }; + if(chandler) { + /* no instance avail, so lets make us avail */ + chandler.callback_available = () => { + return !settings.getValue(Settings.KEY_DISABLE_MULTI_SESSION); + }; - chandler.callback_execute = data => { - preventWelcomeUI = true; - handle_connect_request(data, server_connections.spawn_server_connection()); - return true; + chandler.callback_execute = data => { + preventWelcomeUI = true; + handle_connect_request(data, server_connections.spawn_server_connection()); + return true; + } } } + + preventWelcomeUI = true; + loader.register_task(loader.Stage.LOADED, { + priority: 0, + function: async () => handle_connect_request(connectData, server_connections.active_connection() || server_connections.spawn_server_connection()), + name: tr("default url connect") + }); loader.register_task(loader.Stage.LOADED, task_teaweb_starter); }, priority: 10 }; -const task_certificate_callback: loader.Task = { - name: "certificate accept tester", - function: async () => { - loader.register_task(loader.Stage.LOADED, task_connect_handler); - }, - priority: 10 -}; - loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, { name: "jrendere initialize", function: async () => { @@ -439,16 +335,19 @@ loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, { await initialize(); if(__build.target == "web") { - loader.register_task(loader.Stage.LOADED, task_certificate_callback); + loader.register_task(loader.Stage.LOADED, task_connect_handler); } else { loader.register_task(loader.Stage.LOADED, task_teaweb_starter); } } catch (ex) { - if(ex instanceof Error || typeof(ex.stack) !== "undefined") + if(ex instanceof Error || typeof(ex.stack) !== "undefined") { console.error((tr || (msg => msg))("Critical error stack trace: %o"), ex.stack); + } - if(ex instanceof ReferenceError || ex instanceof TypeError) + if(ex instanceof ReferenceError || ex instanceof TypeError) { ex = ex.name + ": " + ex.message; + } + loader.critical_error("Failed to boot app function:
" + ex); } }, @@ -458,12 +357,12 @@ loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, { loader.register_task(loader.Stage.LOADED, { name: "error task", function: async () => { - if(Settings.instance.static(Settings.KEY_LOAD_DUMMY_ERROR, false)) { - loader.critical_error("The tea is cold!", "Argh, this is evil! Cold tea dosn't taste good."); + if(AppParameters.getValue(AppParameters.KEY_LOAD_DUMMY_ERROR)) { + loader.critical_error("The tea is cold!", "Argh, this is evil! Cold tea does not taste good."); throw "The tea is cold!"; } }, - priority: 20 + priority: 2000 }); loader.register_task(Stage.JAVASCRIPT_INITIALIZING,{ @@ -471,7 +370,7 @@ loader.register_task(Stage.JAVASCRIPT_INITIALIZING,{ function: async () => { try { //Initialize main template const main = $("#tmpl_main").renderTag({ - multi_session: !settings.static_global(Settings.KEY_DISABLE_MULTI_SESSION), + multi_session: !settings.getValue(Settings.KEY_DISABLE_MULTI_SESSION), app_version: __build.version }).dividerfy(); diff --git a/shared/js/profiles/identities/teaspeak-forum.ts b/shared/js/profiles/identities/teaspeak-forum.ts index 469efb4b..ac0ea43b 100644 --- a/shared/js/profiles/identities/teaspeak-forum.ts +++ b/shared/js/profiles/identities/teaspeak-forum.ts @@ -81,7 +81,7 @@ export namespace gcaptcha { } function api_url() { - return settings.static_global(Settings.KEY_TEAFORO_URL); + return settings.getValue(Settings.KEY_TEAFORO_URL); } export class Data { diff --git a/shared/js/settings.ts b/shared/js/settings.ts index e8023222..596e1cc9 100644 --- a/shared/js/settings.ts +++ b/shared/js/settings.ts @@ -5,19 +5,18 @@ import {Stage} from "tc-loader"; import {Registry} from "./events"; import { tr } from "./i18n/localize"; -export type ConfigValueTypes = boolean | number | string | object; -export type ConfigValueTypeNames = "boolean" | "number" | "string" | "object"; +export type RegistryValueType = boolean | number | string | object; +export type RegistryValueTypeNames = "boolean" | "number" | "string" | "object"; -export type ValueTypeMapping = T extends boolean ? "boolean" : - T extends number ? "number" : - T extends string ? "string" : - T extends object ? "object" : never; +export type RegistryValueTypeMapping = T extends boolean ? "boolean" : + T extends number ? "number" : + T extends string ? "string" : + T extends object ? "object" : + never; -export interface SettingsKey { +export interface RegistryKey { key: string; - valueType: ValueTypeMapping; - - defaultValue?: ValueType; + valueType: RegistryValueTypeMapping; fallbackKeys?: string | string[]; fallbackImports?: {[key: string]:(value: string) => ValueType}; @@ -27,111 +26,96 @@ export interface SettingsKey { requireRestart?: boolean; } -export interface ValuedSettingsKey extends SettingsKey { +export interface ValuedRegistryKey extends RegistryKey { defaultValue: ValueType; } -const kNoValuePresent = "--- no value present ---"; +const UPDATE_DIRECT: boolean = true; -export class SettingsBase { - protected static readonly UPDATE_DIRECT: boolean = true; +function decodeValueFromString(input: string, type: RegistryValueTypeMapping) : T { + switch (type) { + case "string": + return input as any; - protected static decodeValueFromString(input: string | undefined, type: ConfigValueTypeNames, defaultValue: DT) : T | DT { - if(input === undefined || input === null) - return defaultValue; + case "boolean": + return (input === "1" || input === "true") as any; - switch (type) { - case "string": - return input as any; + case "number": + return parseFloat(input) as any; - case "boolean": - return (input === "1" || input === "true") as any; - - case "number": - return parseFloat(input) as any; - - case "object": - try { - return JSON.parse(input); - } catch (error) { - return {} as any; - } - - default: - return defaultValue; - } - } - protected static encodeValueToString(input: T | undefined) : string | undefined { - if(input === undefined || input === null) - return undefined; - - switch (typeof input) { - case "string": - return input; - - case "boolean": - return input ? "1" : "0"; - - case "number": - return input.toString(); - - case "object": - return JSON.stringify(input); - - default: - return undefined; - } - } - - protected static resolveKey(key: SettingsKey, - resolver: (key: string) => string | undefined, - defaultValueType: ConfigValueTypeNames, - defaultValue: DefaultType) : ValueType | DefaultType { - let value = resolver(key.key); - if(value === undefined && key.fallbackKeys) { - /* trying fallback values */ - for(const fallback of key.fallbackKeys) { - value = resolver(fallback); - if(value === undefined) - continue; - - if(!key.fallbackImports) - break; - - /* fallback key succeeded */ - const fallbackValueImporter = key.fallbackImports[fallback]; - if(fallbackValueImporter) - return fallbackValueImporter(value); - break; + case "object": + try { + return JSON.parse(input); + } catch (error) { + return {} as any; } - } - return this.decodeValueFromString(value, defaultValueType, defaultValue) as any; + default: + throw "value not decodable"; } } -export class StaticSettings extends SettingsBase { - private static _instance: StaticSettings; - static get instance() : StaticSettings { - if(!this._instance) - this._instance = new StaticSettings(true); - return this._instance; +function encodeValueToString(input: T) : string { + switch (typeof input) { + case "string": + return input; + + case "boolean": + return input ? "1" : "0"; + + case "number": + return input.toString(); + + case "object": + return JSON.stringify(input); + + default: + throw "value has invalid type"; + } +} + +function resolveKey( + key: RegistryKey, + resolver: (key: string) => string | undefined, + defaultValue: DefaultType +) : ValueType | DefaultType { + + let value = resolver(key.key); + if(typeof value === "string") { + return this.decodeValueFromString(value, key.valueType); } - protected _handle: StaticSettings; - protected staticValues = {}; - - protected constructor(_reserved = undefined) { - super(); - if(_reserved && !StaticSettings._instance) { - this.initializeStatic(); - } else { - this._handle = StaticSettings.instance; + /* trying fallback values */ + for(const fallback of key.fallbackKeys) { + value = resolver(fallback); + if(typeof value !== "string") { + continue; } + + if(!key.fallbackImports) { + break; + } + + /* fallback key succeeded */ + const fallbackValueImporter = key.fallbackImports[fallback]; + if(fallbackValueImporter) { + return fallbackValueImporter(value); + } + + break; } - private initializeStatic() { + return defaultValue; +} + +/** + * Switched appended to the application via the URL. + * TODO: Passing native client switches + */ +export namespace AppParameters { + const parameters = {}; + + function parseParameters() { let search; if(window.opener && window.opener !== window) { search = new URL(window.location.href).search; @@ -145,15 +129,130 @@ export class StaticSettings extends SettingsBase { }); } - static(key: SettingsKey, defaultValue: DV) : V | DV; - static(key: ValuedSettingsKey, defaultValue?: V) : V; + export function getValue(key: RegistryKey, defaultValue: DV) : V | DV; + export function getValue(key: ValuedRegistryKey, defaultValue?: V) : V; + export function getValue(key: RegistryKey | ValuedRegistryKey, defaultValue: DV) : V | DV { + if(arguments.length > 1) { + return resolveKey(key, key => parameters[key], defaultValue); + } else if("defaultValue" in key) { + return resolveKey(key, key => parameters[key], key.defaultValue); + } else { + throw tr("missing value"); + } + } - static(key: SettingsKey, defaultValue: DV) : V | DV { - if(this._handle) { - return this._handle.static(key, defaultValue); + parseParameters(); +} + +export namespace AppParameters { + export const KEY_CONNECT_ADDRESS: RegistryKey = { + key: "ca", + fallbackKeys: ["connect_address"], + valueType: "string", + description: "A target address to automatically connect to." + }; + + + export const KEY_CONNECT_NO_SINGLE_INSTANCE: ValuedRegistryKey = { + key: "cnsi", + fallbackKeys: ["connect_no_single_instance"], + defaultValue: false, + valueType: "boolean", + }; + + export const KEY_CONNECT_TYPE: ValuedRegistryKey = { + 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 = { + key: "cn", + fallbackKeys: ["connect_username"], + valueType: "string" + }; + + export const KEY_CONNECT_TOKEN: RegistryKey = { + key: "ctk", + fallbackKeys: ["connect_token"], + valueType: "string", + description: "Token which will be used by default if the connection attempt succeeded." + }; + + export const KEY_CONNECT_PROFILE: RegistryKey = { + key: "cp", + fallbackKeys: ["connect_profile"], + valueType: "string", + description: "The profile which should be used upon connect attempt." + }; + + export const KEY_CONNECT_SERVER_PASSWORD: RegistryKey = { + key: "csp", + fallbackKeys: ["connect_server_password"], + valueType: "string", + description: "The password (hashed) for the auto connect attempt." + }; + + export const KEY_CONNECT_CHANNEL: RegistryKey = { + key: "cc", + fallbackKeys: ["connect_channel"], + valueType: "string", + description: "The target channel for the connect attempt." + }; + + export const KEY_CONNECT_CHANNEL_PASSWORD: RegistryKey = { + key: "ccp", + fallbackKeys: ["connect_channel_password"], + valueType: "string", + description: "The target channel password (hashed) for the connect attempt." + }; + + + export const KEY_IPC_REMOTE_ADDRESS: RegistryKey = { + key: "ipc-address", + valueType: "string", + description: "Address of the owner for IPC communication." + }; + + export const KEY_MODAL_TARGET: RegistryKey = { + key: "modal-target", + valueType: "string", + description: "Target modal unique id which should be loaded" + }; + + export const KEY_LOAD_DUMMY_ERROR: ValuedRegistryKey = { + key: "dummy_load_error", + description: "Triggers a loading error at the end of the loading process.", + valueType: "boolean", + defaultValue: false + }; +} + +export class StaticSettings { + private static _instance: StaticSettings; + static get instance() : StaticSettings { + if(!this._instance) { + this._instance = new StaticSettings(true); } - return StaticSettings.resolveKey(key, key => this.staticValues[key], key.valueType, arguments.length > 1 ? defaultValue : key.defaultValue); + return this._instance; + } + + protected staticValues = {}; + + protected constructor(_reserved = undefined) { } + + static(key: RegistryKey, defaultValue: DV) : V | DV; + static(key: ValuedRegistryKey, defaultValue?: V) : V; + + static(key: RegistryKey | ValuedRegistryKey, defaultValue: DV) : V | DV { + if(arguments.length > 1) { + return AppParameters.getValue(key, defaultValue); + } else { + return AppParameters.getValue(key as ValuedRegistryKey); + } } } @@ -169,421 +268,411 @@ export interface SettingsEvents { } } -export class Settings extends StaticSettings { - static readonly KEY_USER_IS_NEW: ValuedSettingsKey = { - key: 'user_is_new_user', +export class Settings { + static readonly KEY_USER_IS_NEW: ValuedRegistryKey = { + key: "user_is_new_user", valueType: "boolean", defaultValue: true }; - static readonly KEY_LOG_LEVEL: SettingsKey = { - key: 'log.level', + static readonly KEY_LOG_LEVEL: RegistryKey = { + key: "log.level", valueType: "number" }; - static readonly KEY_DISABLE_COSMETIC_SLOWDOWN: ValuedSettingsKey = { - key: 'disable_cosmetic_slowdown', - description: 'Disable the cosmetic slowdows in some processes, like icon upload.', + static readonly KEY_DISABLE_COSMETIC_SLOWDOWN: ValuedRegistryKey = { + key: "disable_cosmetic_slowdown", + description: "Disable the cosmetic slowdows in some processes, like icon upload.", valueType: "boolean", defaultValue: false }; - static readonly KEY_DISABLE_CONTEXT_MENU: ValuedSettingsKey = { - key: 'disableContextMenu', - description: 'Disable the context menu for the channel tree which allows to debug the DOM easier', + static readonly KEY_DISABLE_CONTEXT_MENU: ValuedRegistryKey = { + key: "disableContextMenu", + description: "Disable the context menu for the channel tree which allows to debug the DOM easier", defaultValue: false, valueType: "boolean", }; - static readonly KEY_DISABLE_GLOBAL_CONTEXT_MENU: ValuedSettingsKey = { - key: 'disableGlobalContextMenu', - description: 'Disable the general context menu', + static readonly KEY_DISABLE_GLOBAL_CONTEXT_MENU: ValuedRegistryKey = { + key: "disableGlobalContextMenu", + description: "Disable the general context menu", defaultValue: true, valueType: "boolean", }; - static readonly KEY_DISABLE_UNLOAD_DIALOG: ValuedSettingsKey = { - key: 'disableUnloadDialog', - description: 'Disables the unload popup on side closing', + static readonly KEY_DISABLE_UNLOAD_DIALOG: ValuedRegistryKey = { + key: "disableUnloadDialog", + description: "Disables the unload popup on side closing", valueType: "boolean", defaultValue: false }; - static readonly KEY_DISABLE_VOICE: ValuedSettingsKey = { - key: 'disableVoice', - description: 'Disables the voice bridge. If disabled, the audio and codec workers aren\'t required anymore', + + static readonly KEY_DISABLE_VOICE: ValuedRegistryKey = { + key: "disableVoice", + description: "Disables the voice bridge. If disabled, the audio and codec workers aren\'t required anymore", valueType: "boolean", defaultValue: false }; - static readonly KEY_DISABLE_MULTI_SESSION: ValuedSettingsKey = { - key: 'disableMultiSession', + + static readonly KEY_DISABLE_MULTI_SESSION: ValuedRegistryKey = { + key: "disableMultiSession", defaultValue: false, requireRestart: true, valueType: "boolean", }; - static readonly KEY_LOAD_DUMMY_ERROR: ValuedSettingsKey = { - key: 'dummy_load_error', - description: 'Triggers a loading error at the end of the loading process.', - valueType: "boolean", - defaultValue: false - }; - - static readonly KEY_I18N_DEFAULT_REPOSITORY: ValuedSettingsKey = { - key: 'i18n.default_repository', + static readonly KEY_I18N_DEFAULT_REPOSITORY: ValuedRegistryKey = { + key: "i18n.default_repository", valueType: "string", defaultValue: "https://web.teaspeak.de/i18n/" }; /* Default client states */ - static readonly KEY_CLIENT_STATE_MICROPHONE_MUTED: ValuedSettingsKey = { - key: 'client_state_microphone_muted', + static readonly KEY_CLIENT_STATE_MICROPHONE_MUTED: ValuedRegistryKey = { + key: "client_state_microphone_muted", defaultValue: false, fallbackKeys: ["mute_input"], valueType: "boolean", }; - static readonly KEY_CLIENT_STATE_SPEAKER_MUTED: ValuedSettingsKey = { - key: 'client_state_speaker_muted', + static readonly KEY_CLIENT_STATE_SPEAKER_MUTED: ValuedRegistryKey = { + key: "client_state_speaker_muted", defaultValue: false, fallbackKeys: ["mute_output"], valueType: "boolean", }; - static readonly KEY_CLIENT_STATE_QUERY_SHOWN: ValuedSettingsKey = { - key: 'client_state_query_shown', + static readonly KEY_CLIENT_STATE_QUERY_SHOWN: ValuedRegistryKey = { + key: "client_state_query_shown", defaultValue: false, fallbackKeys: ["show_server_queries"], valueType: "boolean", }; - static readonly KEY_CLIENT_STATE_SUBSCRIBE_ALL_CHANNELS: ValuedSettingsKey = { - key: 'client_state_subscribe_all_channels', + static readonly KEY_CLIENT_STATE_SUBSCRIBE_ALL_CHANNELS: ValuedRegistryKey = { + key: "client_state_subscribe_all_channels", defaultValue: true, fallbackKeys: ["channel_subscribe_all"], valueType: "boolean", }; - static readonly KEY_CLIENT_STATE_AWAY: ValuedSettingsKey = { - key: 'client_state_away', + static readonly KEY_CLIENT_STATE_AWAY: ValuedRegistryKey = { + key: "client_state_away", defaultValue: false, valueType: "boolean", }; - static readonly KEY_CLIENT_AWAY_MESSAGE: ValuedSettingsKey = { - key: 'client_away_message', + static readonly KEY_CLIENT_AWAY_MESSAGE: ValuedRegistryKey = { + key: "client_away_message", defaultValue: "", valueType: "string" }; /* Connect parameters */ - static readonly KEY_FLAG_CONNECT_DEFAULT: ValuedSettingsKey = { - key: 'connect_default', + static readonly KEY_FLAG_CONNECT_DEFAULT: ValuedRegistryKey = { + key: "connect_default", valueType: "boolean", defaultValue: false }; - static readonly KEY_CONNECT_ADDRESS: ValuedSettingsKey = { - key: 'connect_address', + static readonly KEY_CONNECT_ADDRESS: ValuedRegistryKey = { + key: "connect_address", valueType: "string", defaultValue: undefined }; - static readonly KEY_CONNECT_PROFILE: ValuedSettingsKey = { - key: 'connect_profile', - defaultValue: 'default', + static readonly KEY_CONNECT_PROFILE: ValuedRegistryKey = { + key: "connect_profile", + defaultValue: "default", valueType: "string", }; - static readonly KEY_CONNECT_USERNAME: ValuedSettingsKey = { - key: 'connect_username', + static readonly KEY_CONNECT_USERNAME: ValuedRegistryKey = { + key: "connect_username", valueType: "string", defaultValue: undefined }; - static readonly KEY_CONNECT_PASSWORD: ValuedSettingsKey = { - key: 'connect_password', + static readonly KEY_CONNECT_PASSWORD: ValuedRegistryKey = { + key: "connect_password", valueType: "string", defaultValue: undefined }; - static readonly KEY_FLAG_CONNECT_PASSWORD: ValuedSettingsKey = { - key: 'connect_password_hashed', + static readonly KEY_FLAG_CONNECT_PASSWORD: ValuedRegistryKey = { + key: "connect_password_hashed", valueType: "boolean", defaultValue: false }; - static readonly KEY_CONNECT_HISTORY: ValuedSettingsKey = { - key: 'connect_history', + static readonly KEY_CONNECT_HISTORY: ValuedRegistryKey = { + key: "connect_history", valueType: "string", defaultValue: "" }; - static readonly KEY_CONNECT_SHOW_HISTORY: ValuedSettingsKey = { - key: 'connect_show_last_servers', + static readonly KEY_CONNECT_SHOW_HISTORY: ValuedRegistryKey = { + key: "connect_show_last_servers", valueType: "boolean", defaultValue: false }; - static readonly KEY_CONNECT_NO_SINGLE_INSTANCE: ValuedSettingsKey = { - key: 'connect_no_single_instance', + + static readonly KEY_CONNECT_NO_DNSPROXY: ValuedRegistryKey = { + key: "connect_no_dnsproxy", defaultValue: false, valueType: "boolean", }; - static readonly KEY_CONNECT_NO_DNSPROXY: ValuedSettingsKey = { - key: 'connect_no_dnsproxy', - defaultValue: false, - valueType: "boolean", - }; - - static readonly KEY_CERTIFICATE_CALLBACK: ValuedSettingsKey = { - key: 'certificate_callback', + static readonly KEY_CERTIFICATE_CALLBACK: ValuedRegistryKey = { + key: "certificate_callback", valueType: "string", defaultValue: undefined }; /* sounds */ - static readonly KEY_SOUND_MASTER: ValuedSettingsKey = { - key: 'audio_master_volume', + static readonly KEY_SOUND_MASTER: ValuedRegistryKey = { + key: "audio_master_volume", defaultValue: 100, valueType: "number", }; - static readonly KEY_SOUND_MASTER_SOUNDS: ValuedSettingsKey = { - key: 'audio_master_volume_sounds', + static readonly KEY_SOUND_MASTER_SOUNDS: ValuedRegistryKey = { + key: "audio_master_volume_sounds", defaultValue: 100, valueType: "number", }; - static readonly KEY_SOUND_VOLUMES: SettingsKey = { - key: 'sound_volume', + static readonly KEY_SOUND_VOLUMES: RegistryKey = { + key: "sound_volume", valueType: "string", }; - static readonly KEY_CHAT_FIXED_TIMESTAMPS: ValuedSettingsKey = { - key: 'chat_fixed_timestamps', + static readonly KEY_CHAT_FIXED_TIMESTAMPS: ValuedRegistryKey = { + key: "chat_fixed_timestamps", defaultValue: false, - description: 'Enables fixed timestamps for chat messages and disabled the updating once (2 seconds ago... etc)', + description: "Enables fixed timestamps for chat messages and disabled the updating once (2 seconds ago... etc)", valueType: "boolean", }; - static readonly KEY_CHAT_COLLOQUIAL_TIMESTAMPS: ValuedSettingsKey = { - key: 'chat_colloquial_timestamps', + static readonly KEY_CHAT_COLLOQUIAL_TIMESTAMPS: ValuedRegistryKey = { + key: "chat_colloquial_timestamps", defaultValue: true, - description: 'Enabled colloquial timestamp formatting like "Yesterday at ..." or "Today at ..."', + description: "Enabled colloquial timestamp formatting like \"Yesterday at ...\" or \"Today at ...\"", valueType: "boolean", }; - static readonly KEY_CHAT_COLORED_EMOJIES: ValuedSettingsKey = { - key: 'chat_colored_emojies', + static readonly KEY_CHAT_COLORED_EMOJIES: ValuedRegistryKey = { + key: "chat_colored_emojies", defaultValue: true, - description: 'Enables colored emojies powered by Twemoji', + description: "Enables colored emojies powered by Twemoji", valueType: "boolean", }; - static readonly KEY_CHAT_HIGHLIGHT_CODE: ValuedSettingsKey = { - key: 'chat_highlight_code', + static readonly KEY_CHAT_HIGHLIGHT_CODE: ValuedRegistryKey = { + key: "chat_highlight_code", defaultValue: true, - description: 'Enables code highlighting within the chat (Client restart required)', + description: "Enables code highlighting within the chat (Client restart required)", valueType: "boolean", }; - static readonly KEY_CHAT_TAG_URLS: ValuedSettingsKey = { - key: 'chat_tag_urls', + static readonly KEY_CHAT_TAG_URLS: ValuedRegistryKey = { + key: "chat_tag_urls", defaultValue: true, - description: 'Automatically link urls with [url]', + description: "Automatically link urls with [url]", valueType: "boolean", }; - static readonly KEY_CHAT_ENABLE_MARKDOWN: ValuedSettingsKey = { - key: 'chat_enable_markdown', + static readonly KEY_CHAT_ENABLE_MARKDOWN: ValuedRegistryKey = { + key: "chat_enable_markdown", defaultValue: true, - description: 'Enabled markdown chat support.', + description: "Enabled markdown chat support.", valueType: "boolean", }; - static readonly KEY_CHAT_ENABLE_BBCODE: ValuedSettingsKey = { - key: 'chat_enable_bbcode', + static readonly KEY_CHAT_ENABLE_BBCODE: ValuedRegistryKey = { + key: "chat_enable_bbcode", defaultValue: false, - description: 'Enabled bbcode support in chat.', + description: "Enabled bbcode support in chat.", valueType: "boolean", }; - static readonly KEY_CHAT_IMAGE_WHITELIST_REGEX: ValuedSettingsKey = { - key: 'chat_image_whitelist_regex', + static readonly KEY_CHAT_IMAGE_WHITELIST_REGEX: ValuedRegistryKey = { + key: "chat_image_whitelist_regex", defaultValue: JSON.stringify([]), valueType: "string", }; - static readonly KEY_SWITCH_INSTANT_CHAT: ValuedSettingsKey = { - key: 'switch_instant_chat', + static readonly KEY_SWITCH_INSTANT_CHAT: ValuedRegistryKey = { + key: "switch_instant_chat", defaultValue: true, - description: 'Directly switch to channel chat on channel select', + description: "Directly switch to channel chat on channel select", valueType: "boolean", }; - static readonly KEY_SWITCH_INSTANT_CLIENT: ValuedSettingsKey = { - key: 'switch_instant_client', + static readonly KEY_SWITCH_INSTANT_CLIENT: ValuedRegistryKey = { + key: "switch_instant_client", defaultValue: true, - description: 'Directly switch to client info on client select', + description: "Directly switch to client info on client select", valueType: "boolean", }; - static readonly KEY_HOSTBANNER_BACKGROUND: ValuedSettingsKey = { - key: 'hostbanner_background', + static readonly KEY_HOSTBANNER_BACKGROUND: ValuedRegistryKey = { + key: "hostbanner_background", defaultValue: false, - description: 'Enables a default background begind the hostbanner', + description: "Enables a default background begind the hostbanner", valueType: "boolean", }; - static readonly KEY_CHANNEL_EDIT_ADVANCED: ValuedSettingsKey = { - key: 'channel_edit_advanced', + static readonly KEY_CHANNEL_EDIT_ADVANCED: ValuedRegistryKey = { + key: "channel_edit_advanced", defaultValue: false, - description: 'Edit channels in advanced mode with a lot more settings', + description: "Edit channels in advanced mode with a lot more settings", valueType: "boolean", }; - static readonly KEY_PERMISSIONS_SHOW_ALL: ValuedSettingsKey = { - key: 'permissions_show_all', + static readonly KEY_PERMISSIONS_SHOW_ALL: ValuedRegistryKey = { + key: "permissions_show_all", defaultValue: false, - description: 'Show all permissions even thou they dont make sense for the server/channel group', + description: "Show all permissions even thou they dont make sense for the server/channel group", valueType: "boolean", }; - static readonly KEY_TEAFORO_URL: ValuedSettingsKey = { + static readonly KEY_TEAFORO_URL: ValuedRegistryKey = { key: "teaforo_url", defaultValue: "https://forum.teaspeak.de/", valueType: "string", }; - static readonly KEY_FONT_SIZE: ValuedSettingsKey = { + static readonly KEY_FONT_SIZE: ValuedRegistryKey = { key: "font_size", valueType: "number", defaultValue: 14 //parseInt(getComputedStyle(document.body).fontSize) }; - static readonly KEY_ICON_SIZE: ValuedSettingsKey = { + static readonly KEY_ICON_SIZE: ValuedRegistryKey = { key: "icon_size", defaultValue: 100, valueType: "number", }; - static readonly KEY_KEYCONTROL_DATA: ValuedSettingsKey = { + static readonly KEY_KEYCONTROL_DATA: ValuedRegistryKey = { key: "keycontrol_data", defaultValue: "{}", valueType: "string", }; - static readonly KEY_LAST_INVITE_LINK_TYPE: ValuedSettingsKey = { + static readonly KEY_LAST_INVITE_LINK_TYPE: ValuedRegistryKey = { key: "last_invite_link_type", defaultValue: "tea-web", valueType: "string", }; - static readonly KEY_TRANSFERS_SHOW_FINISHED: ValuedSettingsKey = { - key: 'transfers_show_finished', + static readonly KEY_TRANSFERS_SHOW_FINISHED: ValuedRegistryKey = { + key: "transfers_show_finished", defaultValue: true, description: "Show finished file transfers in the file transfer list", valueType: "boolean", }; - static readonly KEY_TRANSFER_DOWNLOAD_FOLDER: SettingsKey = { + static readonly KEY_TRANSFER_DOWNLOAD_FOLDER: RegistryKey = { key: "transfer_download_folder", description: "The download folder for the file transfer downloads", valueType: "string", /* defaultValue: */ }; - static readonly KEY_IPC_REMOTE_ADDRESS: SettingsKey = { + static readonly KEY_IPC_REMOTE_ADDRESS: RegistryKey = { key: "ipc-address", valueType: "string" }; - static readonly KEY_W2G_SIDEBAR_COLLAPSED: ValuedSettingsKey = { - key: 'w2g_sidebar_collapsed', + static readonly KEY_W2G_SIDEBAR_COLLAPSED: ValuedRegistryKey = { + key: "w2g_sidebar_collapsed", defaultValue: false, valueType: "boolean", }; - static readonly KEY_VOICE_ECHO_TEST_ENABLED: ValuedSettingsKey = { - key: 'voice_echo_test_enabled', + static readonly KEY_VOICE_ECHO_TEST_ENABLED: ValuedRegistryKey = { + key: "voice_echo_test_enabled", defaultValue: true, valueType: "boolean", }; - static readonly KEY_RNNOISE_FILTER: ValuedSettingsKey = { - key: 'rnnoise_filter', + static readonly KEY_RNNOISE_FILTER: ValuedRegistryKey = { + key: "rnnoise_filter", defaultValue: true, - description: 'Enable the rnnoise filter for supressing background noise', + description: "Enable the rnnoise filter for supressing background noise", valueType: "boolean", }; - static readonly KEY_LOADER_ANIMATION_ABORT: ValuedSettingsKey = { - key: 'loader_animation_abort', + static readonly KEY_LOADER_ANIMATION_ABORT: ValuedRegistryKey = { + key: "loader_animation_abort", defaultValue: false, - description: 'Abort the loader animation when the app has been finished loading', + description: "Abort the loader animation when the app has been finished loading", valueType: "boolean", }; - static readonly KEY_STOP_VIDEO_ON_SWITCH: ValuedSettingsKey = { - key: 'stop_video_on_channel_switch', + static readonly KEY_STOP_VIDEO_ON_SWITCH: ValuedRegistryKey = { + key: "stop_video_on_channel_switch", defaultValue: true, - description: 'Stop video broadcasting on channel switch', + description: "Stop video broadcasting on channel switch", valueType: "boolean", }; - static readonly KEY_VIDEO_SHOW_ALL_CLIENTS: ValuedSettingsKey = { - key: 'video_show_all_clients', + static readonly KEY_VIDEO_SHOW_ALL_CLIENTS: ValuedRegistryKey = { + key: "video_show_all_clients", defaultValue: false, description: "Show all clients within the video frame, even if they're not broadcasting video", valueType: "boolean", }; - static readonly KEY_VIDEO_FORCE_SHOW_OWN_VIDEO: ValuedSettingsKey = { - key: 'video_force_show_own_video', + static readonly KEY_VIDEO_FORCE_SHOW_OWN_VIDEO: ValuedRegistryKey = { + key: "video_force_show_own_video", defaultValue: true, description: "Show own video preview even if you're not broadcasting any video", valueType: "boolean", }; - static readonly KEY_VIDEO_AUTO_SUBSCRIBE_MODE: ValuedSettingsKey = { - key: 'video_auto_subscribe_mode', + static readonly KEY_VIDEO_AUTO_SUBSCRIBE_MODE: ValuedRegistryKey = { + 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", }; - static readonly KEY_VIDEO_DEFAULT_MAX_WIDTH: ValuedSettingsKey = { - key: 'video_default_max_width', + static readonly KEY_VIDEO_DEFAULT_MAX_WIDTH: ValuedRegistryKey = { + key: "video_default_max_width", defaultValue: 1280, description: "The default maximal width of the video being crated.", valueType: "number", }; - static readonly KEY_VIDEO_DEFAULT_MAX_HEIGHT: ValuedSettingsKey = { - key: 'video_default_max_height', + static readonly KEY_VIDEO_DEFAULT_MAX_HEIGHT: ValuedRegistryKey = { + key: "video_default_max_height", defaultValue: 720, description: "The default maximal height of the video being crated.", valueType: "number", }; - static readonly KEY_VIDEO_DYNAMIC_QUALITY: ValuedSettingsKey = { - key: 'video_dynamic_quality', + static readonly KEY_VIDEO_DYNAMIC_QUALITY: ValuedRegistryKey = { + key: "video_dynamic_quality", defaultValue: true, description: "Dynamically decrease video quality in order to archive a higher framerate.", valueType: "boolean", }; - static readonly KEY_VIDEO_DYNAMIC_FRAME_RATE: ValuedSettingsKey = { - key: 'video_dynamic_frame_rate', + static readonly KEY_VIDEO_DYNAMIC_FRAME_RATE: ValuedRegistryKey = { + key: "video_dynamic_frame_rate", defaultValue: true, description: "Dynamically decrease video framerate to allow higher video resolutions.", valueType: "boolean", }; - static readonly KEY_VIDEO_QUICK_SETUP: ValuedSettingsKey = { - key: 'video_quick_setup', + static readonly KEY_VIDEO_QUICK_SETUP: ValuedRegistryKey = { + key: "video_quick_setup", defaultValue: true, description: "Automatically select the default video device and start broadcasting without the video configure dialog.", valueType: "boolean", }; - static readonly FN_LOG_ENABLED: (category: string) => SettingsKey = category => { + static readonly FN_LOG_ENABLED: (category: string) => RegistryKey = category => { return { key: "log." + category.toLowerCase() + ".enabled", valueType: "boolean", } }; - static readonly FN_SEPARATOR_STATE: (separator: string) => SettingsKey = separator => { + static readonly FN_SEPARATOR_STATE: (separator: string) => RegistryKey = separator => { return { key: "separator-settings-" + separator, valueType: "string", @@ -591,50 +680,50 @@ export class Settings extends StaticSettings { } }; - static readonly FN_LOG_LEVEL_ENABLED: (category: string) => SettingsKey = category => { + static readonly FN_LOG_LEVEL_ENABLED: (category: string) => RegistryKey = category => { return { key: "log.level." + category.toLowerCase() + ".enabled", valueType: "boolean" } }; - static readonly FN_INVITE_LINK_SETTING: (name: string) => SettingsKey = name => { + static readonly FN_INVITE_LINK_SETTING: (name: string) => RegistryKey = name => { return { - key: 'invite_link_setting_' + name, + key: "invite_link_setting_" + name, valueType: "string", } }; - static readonly FN_SERVER_CHANNEL_SUBSCRIBE_MODE: (channel_id: number) => SettingsKey = channel => { + static readonly FN_SERVER_CHANNEL_SUBSCRIBE_MODE: (channel_id: number) => RegistryKey = channel => { return { - key: 'channel_subscribe_mode_' + channel, + key: "channel_subscribe_mode_" + channel, valueType: "number", } }; - static readonly FN_SERVER_CHANNEL_COLLAPSED: (channel_id: number) => ValuedSettingsKey = channel => { + static readonly FN_SERVER_CHANNEL_COLLAPSED: (channel_id: number) => ValuedRegistryKey = channel => { return { - key: 'channel_collapsed_' + channel, + key: "channel_collapsed_" + channel, defaultValue: false, valueType: "boolean", } }; - static readonly FN_PROFILE_RECORD: (name: string) => SettingsKey = name => { + static readonly FN_PROFILE_RECORD: (name: string) => RegistryKey = name => { return { - key: 'profile_record' + name, + key: "profile_record" + name, valueType: "object", } }; - static readonly FN_CHANNEL_CHAT_READ: (id: number) => SettingsKey = id => { + static readonly FN_CHANNEL_CHAT_READ: (id: number) => RegistryKey = id => { return { - key: 'channel_chat_read_' + id, + key: "channel_chat_read_" + id, valueType: "number", } }; - static readonly FN_CLIENT_MUTED: (clientUniqueId: string) => SettingsKey = clientUniqueId => { + static readonly FN_CLIENT_MUTED: (clientUniqueId: string) => RegistryKey = clientUniqueId => { return { key: "client_" + clientUniqueId + "_muted", valueType: "boolean", @@ -642,7 +731,7 @@ export class Settings extends StaticSettings { } }; - static readonly FN_CLIENT_VOLUME: (clientUniqueId: string) => SettingsKey = clientUniqueId => { + static readonly FN_CLIENT_VOLUME: (clientUniqueId: string) => RegistryKey = clientUniqueId => { return { key: "client_" + clientUniqueId + "_volume", valueType: "number", @@ -650,21 +739,21 @@ export class Settings extends StaticSettings { } }; - static readonly FN_EVENTS_NOTIFICATION_ENABLED: (event: string) => SettingsKey = event => { + static readonly FN_EVENTS_NOTIFICATION_ENABLED: (event: string) => RegistryKey = event => { return { key: "event_notification_" + event + "_enabled", valueType: "boolean" } }; - static readonly FN_EVENTS_LOG_ENABLED: (event: string) => SettingsKey = event => { + static readonly FN_EVENTS_LOG_ENABLED: (event: string) => RegistryKey = event => { return { key: "event_log_" + event + "_enabled", valueType: "boolean" } }; - static readonly FN_EVENTS_FOCUS_ENABLED: (event: string) => SettingsKey = event => { + static readonly FN_EVENTS_FOCUS_ENABLED: (event: string) => RegistryKey = event => { return { key: "event_focus_" + event + "_enabled", valueType: "boolean" @@ -675,8 +764,9 @@ export class Settings extends StaticSettings { const result = []; for(const key of Object.keys(Settings)) { - if(!key.toUpperCase().startsWith("KEY_")) + if(!key.toUpperCase().startsWith("KEY_")) { continue; + } result.push(key); } @@ -690,14 +780,14 @@ export class Settings extends StaticSettings { (window as any).Settings = Settings; } + readonly events: Registry; + private readonly cacheGlobal = {}; private saveWorker: number; private updated: boolean = false; constructor() { - super(); - this.events = new Registry(); const json = localStorage.getItem("settings.global"); try { @@ -725,31 +815,35 @@ export class Settings extends StaticSettings { }, 5 * 1000); } - static_global(key: ValuedSettingsKey, defaultValue?: V) : V; - static_global(key: SettingsKey, defaultValue: DV) : V | DV; - static_global(key: SettingsKey | ValuedSettingsKey, defaultValue: DV) : V | DV { - const staticValue = this.static(key, kNoValuePresent); - if(staticValue !== kNoValuePresent) - return staticValue; - - if(arguments.length > 1) - return this.global(key, defaultValue); - return this.global(key as ValuedSettingsKey); + getValue(key: RegistryKey, defaultValue: DV) : V | DV; + getValue(key: ValuedRegistryKey, defaultValue?: V) : V; + getValue(key: RegistryKey | ValuedRegistryKey, defaultValue: DV) : V | DV { + if(arguments.length > 1) { + return resolveKey(key, key => this.cacheGlobal[key], defaultValue); + } else if("defaultValue" in key) { + return resolveKey(key, key => this.cacheGlobal[key], key.defaultValue); + } else { + throw tr("missing default value"); + } } - global(key: SettingsKey, defaultValue: DV) : V | DV; - global(key: ValuedSettingsKey, defaultValue?: V) : V; - global(key: SettingsKey, defaultValue: DV) : V | DV { - return StaticSettings.resolveKey(key, key => this.cacheGlobal[key], key.valueType, arguments.length > 1 ? defaultValue : key.defaultValue); - } + setValue(key: RegistryKey, value?: T){ + if(value === null) { + value = undefined; + } - changeGlobal(key: SettingsKey, value?: T){ - if(this.cacheGlobal[key.key] === value) + if(this.cacheGlobal[key.key] === value) { return; + } + + const oldValue = this.cacheGlobal[key.key]; + if(value === undefined) { + delete this.cacheGlobal[key.key]; + } else { + this.cacheGlobal[key.key] = encodeValueToString(value); + } this.updated = true; - const oldValue = this.cacheGlobal[key.key]; - this.cacheGlobal[key.key] = StaticSettings.encodeValueToString(value); this.events.fire("notify_setting_changed", { mode: "global", newValue: this.cacheGlobal[key.key], @@ -759,11 +853,12 @@ export class Settings extends StaticSettings { }); logTrace(LogCategory.GENERAL, tr("Changing global setting %s to %o"), key.key, value); - if(Settings.UPDATE_DIRECT) + if(UPDATE_DIRECT) { this.save(); + } } - globalChangeListener(key: SettingsKey, listener: (newValue: T) => void) : () => void { + globalChangeListener(key: RegistryKey, listener: (newValue: T) => void) : () => void { return this.events.on("notify_setting_changed", event => { if(event.setting === key.key && event.mode === "global") { listener(event.newCastedValue); @@ -780,61 +875,76 @@ export class Settings extends StaticSettings { } } -export class ServerSettings extends SettingsBase { +export class ServerSettings { private cacheServer = {}; - private _server_unique_id: string; - private _server_save_worker: number; - private _server_settings_updated: boolean = false; + private serverUniqueId: string; + private serverSaveWorker: number; + private serverSettingsUpdated: boolean = false; private _destroyed = false; constructor() { - super(); - this._server_save_worker = setInterval(() => { - if(this._server_settings_updated) + this.serverSaveWorker = setInterval(() => { + if(this.serverSettingsUpdated) { this.save(); + } }, 5 * 1000); } destroy() { this._destroyed = true; - this._server_unique_id = undefined; + this.serverUniqueId = undefined; this.cacheServer = undefined; - clearInterval(this._server_save_worker); - this._server_save_worker = undefined; + clearInterval(this.serverSaveWorker); + this.serverSaveWorker = undefined; } - server(key: SettingsKey, defaultValue?: DV) : V | DV { - if(this._destroyed) + getValue(key: RegistryKey | ValuedRegistryKey, defaultValue?: DV) : V | DV { + if(this._destroyed) { throw "destroyed"; + } - return StaticSettings.resolveKey(key, key => this.cacheServer[key], key.valueType, arguments.length > 1 ? defaultValue : key.defaultValue); + if(arguments.length > 1) { + return resolveKey(key, key => this.cacheServer[key], defaultValue); + } else if("defaultValue" in key) { + return resolveKey(key, key => this.cacheServer[key], key.defaultValue); + } else { + throw tr("missing default value"); + } } - changeServer(key: SettingsKey, value?: T) { - if(this._destroyed) throw "destroyed"; + setValue(key: RegistryKey, value?: T) { + if(this._destroyed) { + throw "destroyed"; + } - if(this.cacheServer[key.key] === value) + if(this.cacheServer[key.key] === value) { return; + } - this._server_settings_updated = true; - this.cacheServer[key.key] = StaticSettings.encodeValueToString(value); + this.serverSettingsUpdated = true; + if(value === undefined || value === null) { + delete this.cacheServer[key.key]; + } else { + this.cacheServer[key.key] = encodeValueToString(value); + } - if(Settings.UPDATE_DIRECT) + if(UPDATE_DIRECT) { this.save(); + } } setServer(server_unique_id: string) { if(this._destroyed) throw "destroyed"; - if(this._server_unique_id) { + if(this.serverUniqueId) { this.save(); this.cacheServer = {}; - this._server_unique_id = undefined; + this.serverUniqueId = undefined; } - this._server_unique_id = server_unique_id; + this.serverUniqueId = server_unique_id; - if(this._server_unique_id) { + if(this.serverUniqueId) { const json = localStorage.getItem("settings.server_" + server_unique_id); try { @@ -848,14 +958,18 @@ export class ServerSettings extends SettingsBase { } save() { - if(this._destroyed) throw "destroyed"; - this._server_settings_updated = false; + if(this._destroyed) { + throw "destroyed"; + } + this.serverSettingsUpdated = false; - if(this._server_unique_id) { + if(this.serverUniqueId) { let server = JSON.stringify(this.cacheServer); - localStorage.setItem("settings.server_" + this._server_unique_id, server); - if(localStorage.save) + localStorage.setItem("settings.server_" + this.serverUniqueId, server); + + if(localStorage.save) { localStorage.save(); + } } } } diff --git a/shared/js/sound/Sounds.ts b/shared/js/sound/Sounds.ts index e4ca3cec..d5f399d8 100644 --- a/shared/js/sound/Sounds.ts +++ b/shared/js/sound/Sounds.ts @@ -157,7 +157,7 @@ export function save() { data.overlap = overlap_sounds; data.ignore_muted = ignore_muted; - settings.changeGlobal(Settings.KEY_SOUND_VOLUMES, JSON.stringify(data)); + settings.setValue(Settings.KEY_SOUND_VOLUMES, JSON.stringify(data)); } } @@ -173,7 +173,7 @@ export function initialize() : Promise { /* volumes */ { - const data = JSON.parse(settings.static_global(Settings.KEY_SOUND_VOLUMES, "{}")); + const data = JSON.parse(settings.getValue(Settings.KEY_SOUND_VOLUMES, "{}")); for(const sound_key of Object.keys(Sound)) { if(typeof(data[Sound[sound_key]]) !== "undefined") speech_volume[Sound[sound_key]] = data[Sound[sound_key]]; diff --git a/shared/js/text/bbcode/emoji.tsx b/shared/js/text/bbcode/emoji.tsx index 8bd77fa3..52e5d3c5 100644 --- a/shared/js/text/bbcode/emoji.tsx +++ b/shared/js/text/bbcode/emoji.tsx @@ -46,7 +46,7 @@ loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, { rendererReact.setTextRenderer(new class extends ElementRenderer { render(element: TextElement, renderer: ReactRenderer): React.ReactNode { - if(!settings.static_global(Settings.KEY_CHAT_COLORED_EMOJIES)) + if(!settings.getValue(Settings.KEY_CHAT_COLORED_EMOJIES)) return element.text(); let text = element.text(); diff --git a/shared/js/text/bbcode/highlight.tsx b/shared/js/text/bbcode/highlight.tsx index c0df10ed..20118d3e 100644 --- a/shared/js/text/bbcode/highlight.tsx +++ b/shared/js/text/bbcode/highlight.tsx @@ -6,7 +6,7 @@ import {TagElement} from "vendor/xbbcode/elements"; import * as React from "react"; import {tra} from "tc-shared/i18n/localize"; import * as DOMPurify from "dompurify"; -import {copy_to_clipboard} from "tc-shared/utils/helpers"; +import {copyToClipboard} from "tc-shared/utils/helpers"; import {rendererReact, rendererText} from "tc-shared/text/bbcode/renderer"; import {MenuEntryType, spawn_context_menu} from "tc-shared/ui/elements/ContextMenu"; @@ -87,7 +87,7 @@ loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, { function: async () => { let reactId = 0; - if(!settings.static_global(Settings.KEY_CHAT_HIGHLIGHT_CODE)) { + if(!settings.getValue(Settings.KEY_CHAT_HIGHLIGHT_CODE)) { return; } /* override default parser */ @@ -127,7 +127,7 @@ loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, { onContextMenu={event => { event.preventDefault(); spawn_context_menu(event.pageX, event.pageY, { - callback: () => copy_to_clipboard(lines.join("\n")), + callback: () => copyToClipboard(lines.join("\n")), name: tr("Copy code"), type: MenuEntryType.ENTRY, icon_class: "client-copy" diff --git a/shared/js/text/bbcode/image.tsx b/shared/js/text/bbcode/image.tsx index 2cab859c..a771d64a 100644 --- a/shared/js/text/bbcode/image.tsx +++ b/shared/js/text/bbcode/image.tsx @@ -4,7 +4,7 @@ import * as React from "react"; import * as loader from "tc-loader"; import {rendererReact, rendererText} from "tc-shared/text/bbcode/renderer"; import * as contextmenu from "tc-shared/ui/elements/ContextMenu"; -import {copy_to_clipboard} from "tc-shared/utils/helpers"; +import {copyToClipboard} from "tc-shared/utils/helpers"; import * as image_preview from "tc-shared/ui/frames/image_preview"; export const regexImage = /^(?:https?):(?:\/{1,3}|\\)[-a-zA-Z0-9:;,@#%&()~_?+=\/\\.]*$/g; @@ -43,7 +43,7 @@ function loadImageForElement(element: HTMLImageElement) { type: contextmenu.MenuEntryType.ENTRY, icon_class: "client-browse-addon-online" }, contextmenu.Entry.HR(), { - callback: () => copy_to_clipboard(url), + callback: () => copyToClipboard(url), name: tr("Copy image URL to clipboard"), type: contextmenu.MenuEntryType.ENTRY, icon_class: "client-copy" diff --git a/shared/js/text/bbcode/url.tsx b/shared/js/text/bbcode/url.tsx index 628d24b9..9987652d 100644 --- a/shared/js/text/bbcode/url.tsx +++ b/shared/js/text/bbcode/url.tsx @@ -1,5 +1,5 @@ import * as contextmenu from "tc-shared/ui/elements/ContextMenu"; -import {copy_to_clipboard} from "tc-shared/utils/helpers"; +import {copyToClipboard} from "tc-shared/utils/helpers"; import * as loader from "tc-loader"; import {ElementRenderer} from "vendor/xbbcode/renderer/base"; import {TagElement} from "vendor/xbbcode/elements"; @@ -26,7 +26,7 @@ function spawnUrlContextMenu(pageX: number, pageY: number, target: string) { type: contextmenu.MenuEntryType.ENTRY, visible: __build.target === "client" && false // Currently not possible }, contextmenu.Entry.HR(), { - callback: () => copy_to_clipboard(target), + callback: () => copyToClipboard(target), name: tr("Copy URL to clipboard"), type: contextmenu.MenuEntryType.ENTRY, icon_class: "client-copy" diff --git a/shared/js/text/bbcode/youtube.tsx b/shared/js/text/bbcode/youtube.tsx index 4c14b7d5..800fcf4c 100644 --- a/shared/js/text/bbcode/youtube.tsx +++ b/shared/js/text/bbcode/youtube.tsx @@ -7,7 +7,7 @@ import {BBCodeRenderer} from "tc-shared/text/bbcode"; import {HTMLRenderer} from "tc-shared/ui/react-elements/HTMLRenderer"; import * as contextmenu from "tc-shared/ui/elements/ContextMenu"; import {spawn_context_menu} from "tc-shared/ui/elements/ContextMenu"; -import {copy_to_clipboard} from "tc-shared/utils/helpers"; +import {copyToClipboard} from "tc-shared/utils/helpers"; import {global_client_actions} from "tc-shared/events/GlobalEvents"; import {server_connections} from "tc-shared/ConnectionManager"; @@ -61,7 +61,7 @@ export const YoutubeRenderer = (props: { children?: React.ReactElement | React.R type: contextmenu.MenuEntryType.ENTRY, icon_class: "client-browse-addon-online" }, contextmenu.Entry.HR(), { - callback: () => copy_to_clipboard(props.url), + callback: () => copyToClipboard(props.url), name: tr("Copy video URL to clipboard"), type: contextmenu.MenuEntryType.ENTRY, icon_class: "client-copy" diff --git a/shared/js/text/chat.ts b/shared/js/text/chat.ts index bafca40f..ceaf9c0a 100644 --- a/shared/js/text/chat.ts +++ b/shared/js/text/chat.ts @@ -69,8 +69,8 @@ function bbcodeLinkUrls(message: string, ignore: { start: number, end: number }[ } export function preprocessChatMessageForSend(message: string) : string { - const parseMarkdown = settings.static_global(Settings.KEY_CHAT_ENABLE_MARKDOWN); - const escapeBBCodes = !settings.static_global(Settings.KEY_CHAT_ENABLE_BBCODE); + const parseMarkdown = settings.getValue(Settings.KEY_CHAT_ENABLE_MARKDOWN); + const escapeBBCodes = !settings.getValue(Settings.KEY_CHAT_ENABLE_BBCODE); if(parseMarkdown) { message = renderMarkdownAsBBCode(message, text => escapeBBCodes ? escapeBBCode(text) : text); @@ -78,7 +78,7 @@ export function preprocessChatMessageForSend(message: string) : string { message = escapeBBCode(message); } - if(settings.static_global(Settings.KEY_CHAT_TAG_URLS)) { + if(settings.getValue(Settings.KEY_CHAT_TAG_URLS)) { const bbcodeElements = parseBBCode(message, {}); const noParseRanges: { start: number, end: number }[] = []; diff --git a/shared/js/tree/Channel.ts b/shared/js/tree/Channel.ts index 826cdc06..81756b6e 100644 --- a/shared/js/tree/Channel.ts +++ b/shared/js/tree/Channel.ts @@ -222,8 +222,8 @@ export class ChannelEntry extends ChannelTreeEntry { }); }); - this.collapsed = this.channelTree.client.settings.server(Settings.FN_SERVER_CHANNEL_COLLAPSED(this.channelId)); - this.subscriptionMode = this.channelTree.client.settings.server(Settings.FN_SERVER_CHANNEL_SUBSCRIBE_MODE(this.channelId)); + this.collapsed = this.channelTree.client.settings.getValue(Settings.FN_SERVER_CHANNEL_COLLAPSED(this.channelId)); + this.subscriptionMode = this.channelTree.client.settings.getValue(Settings.FN_SERVER_CHANNEL_SUBSCRIBE_MODE(this.channelId)); this.channelDescriptionCached = false; this.channelDescriptionCallback = []; @@ -452,7 +452,7 @@ export class ChannelEntry extends ChannelTreeEntry { this.channelTree.client.getChannelConversations().setSelectedConversation(conversation); this.channelTree.client.getSideBar().showChannel(); }, - visible: !settings.static_global(Settings.KEY_SWITCH_INSTANT_CHAT) + visible: !settings.getValue(Settings.KEY_SWITCH_INSTANT_CHAT) }, { type: contextmenu.MenuEntryType.HR, name: '' @@ -776,7 +776,7 @@ export class ChannelEntry extends ChannelTreeEntry { this.collapsed = flag; this.events.fire("notify_collapsed_state_changed", { collapsed: flag }); - this.channelTree.client.settings.changeServer(Settings.FN_SERVER_CHANNEL_COLLAPSED(this.channelId), flag); + this.channelTree.client.settings.setValue(Settings.FN_SERVER_CHANNEL_COLLAPSED(this.channelId), flag); } isSubscribed() : boolean { @@ -803,7 +803,7 @@ export class ChannelEntry extends ChannelTreeEntry { } this.subscriptionMode = mode; - this.channelTree.client.settings.changeServer(Settings.FN_SERVER_CHANNEL_SUBSCRIBE_MODE(this.channelId), mode); + this.channelTree.client.settings.setValue(Settings.FN_SERVER_CHANNEL_SUBSCRIBE_MODE(this.channelId), mode); if(!dontSyncSubscribeMode) { this.updateSubscribeMode().then(undefined); } diff --git a/shared/js/tree/ChannelTree.tsx b/shared/js/tree/ChannelTree.tsx index 217feb0f..132a38db 100644 --- a/shared/js/tree/ChannelTree.tsx +++ b/shared/js/tree/ChannelTree.tsx @@ -171,7 +171,7 @@ export class ChannelTree { this.events.fire("notify_selected_entry_changed", { newEntry: entry, oldEntry: oldEntry }); if(this.selectedEntry instanceof ClientEntry) { - if(settings.static_global(Settings.KEY_SWITCH_INSTANT_CLIENT)) { + if(settings.getValue(Settings.KEY_SWITCH_INSTANT_CLIENT)) { if(this.selectedEntry instanceof MusicClientEntry) { this.client.getSideBar().showMusicPlayer(this.selectedEntry); } else { @@ -179,13 +179,13 @@ export class ChannelTree { } } } else if(this.selectedEntry instanceof ChannelEntry) { - if(settings.static_global(Settings.KEY_SWITCH_INSTANT_CHAT)) { + if(settings.getValue(Settings.KEY_SWITCH_INSTANT_CHAT)) { const conversation = this.client.getChannelConversations().findOrCreateConversation(this.selectedEntry.channelId); this.client.getChannelConversations().setSelectedConversation(conversation); this.client.getSideBar().showChannel(); } } else if(this.selectedEntry instanceof ServerEntry) { - if(settings.static_global(Settings.KEY_SWITCH_INSTANT_CHAT)) { + if(settings.getValue(Settings.KEY_SWITCH_INSTANT_CHAT)) { const conversation = this.client.getChannelConversations().findOrCreateConversation(0); this.client.getChannelConversations().setSelectedConversation(conversation); this.client.getSideBar().showServer(); diff --git a/shared/js/tree/Client.ts b/shared/js/tree/Client.ts index 86911cf0..f67f3c41 100644 --- a/shared/js/tree/Client.ts +++ b/shared/js/tree/Client.ts @@ -345,7 +345,7 @@ export class ClientEntry extends Cha } this.voiceMuted = flagMuted; - this.channelTree.client.settings.changeServer(Settings.FN_CLIENT_MUTED(this.clientUid()), flagMuted); + this.channelTree.client.settings.setValue(Settings.FN_CLIENT_MUTED(this.clientUid()), flagMuted); this.updateVoiceVolume(); this.events.fire("notify_mute_state_change", { muted: flagMuted }); @@ -366,12 +366,12 @@ export class ClientEntry extends Cha this.channelTree.client.getSideBar().showClientInfo(this as any); }, icon_class: "client-about", - visible: !settings.static_global(Settings.KEY_SWITCH_INSTANT_CLIENT) + visible: !settings.getValue(Settings.KEY_SWITCH_INSTANT_CLIENT) }, { callback: () => {}, type: contextmenu.MenuEntryType.HR, name: "", - visible: !settings.static_global(Settings.KEY_SWITCH_INSTANT_CLIENT) + visible: !settings.getValue(Settings.KEY_SWITCH_INSTANT_CLIENT) } ] } @@ -763,8 +763,8 @@ export class ClientEntry extends Cha reorder_channel = true; } if(variable.key == "client_unique_identifier") { - this.voiceVolume = this.channelTree.client.settings.server(Settings.FN_CLIENT_VOLUME(this.clientUid()), 1); - const mute_status = this.channelTree.client.settings.server(Settings.FN_CLIENT_MUTED(this.clientUid()), false); + this.voiceVolume = this.channelTree.client.settings.getValue(Settings.FN_CLIENT_VOLUME(this.clientUid()), 1); + const mute_status = this.channelTree.client.settings.getValue(Settings.FN_CLIENT_MUTED(this.clientUid()), false); this.setMuted(mute_status, mute_status); /* force only needed when we want to mute the client */ this.updateVoiceVolume(); log.debug(LogCategory.CLIENT, tr("Loaded client (%s) server specific properties. Volume: %o Muted: %o."), this.clientUid(), this.voiceVolume, this.voiceMuted); @@ -917,7 +917,7 @@ export class ClientEntry extends Cha this.voiceVolume = value; this.updateVoiceVolume(); - this.channelTree.client.settings.changeServer(Settings.FN_CLIENT_VOLUME(this.clientUid()), value); + this.channelTree.client.settings.setValue(Settings.FN_CLIENT_VOLUME(this.clientUid()), value); this.events.fire("notify_audio_level_changed", { newValue: value }); } @@ -972,7 +972,7 @@ export class LocalClientEntry extends ClientEntry { const old_name = this.properties.client_nickname; this.updateVariables({ key: "client_nickname", value: new_name }); /* change it locally */ return this.handle.serverConnection.send_command("clientupdate", { client_nickname: new_name }).then(() => { - settings.changeGlobal(Settings.KEY_CONNECT_USERNAME, new_name); + settings.setValue(Settings.KEY_CONNECT_USERNAME, new_name); this.channelTree.client.log.log("client.nickname.changed.own", { client: this.log_data(), old_name: old_name, diff --git a/shared/js/tree/Server.ts b/shared/js/tree/Server.ts index 556203c5..c460f60b 100644 --- a/shared/js/tree/Server.ts +++ b/shared/js/tree/Server.ts @@ -221,7 +221,7 @@ export class ServerEntry extends ChannelTreeEntry { this.channelTree.client.getChannelConversations().setSelectedConversation(this.channelTree.client.getChannelConversations().findOrCreateConversation(0)); this.channelTree.client.getSideBar().showServer(); }, - visible: !settings.static_global(Settings.KEY_SWITCH_INSTANT_CHAT) + visible: !settings.getValue(Settings.KEY_SWITCH_INSTANT_CHAT) }, { type: contextmenu.MenuEntryType.ENTRY, icon_class: "client-virtualserver_edit", diff --git a/shared/js/ui/elements/ContextDivider.ts b/shared/js/ui/elements/ContextDivider.ts index f421db0f..877fb8f0 100644 --- a/shared/js/ui/elements/ContextDivider.ts +++ b/shared/js/ui/elements/ContextDivider.ts @@ -89,7 +89,7 @@ if(!$.fn.dividerfy) { apply_view(property, previous_p, next_p); if(seperator_id) - settings.changeGlobal(Settings.FN_SEPARATOR_STATE(seperator_id), JSON.stringify({ + settings.setValue(Settings.FN_SEPARATOR_STATE(seperator_id), JSON.stringify({ previous: previous_p, next: next_p, property: property @@ -130,7 +130,7 @@ if(!$.fn.dividerfy) { if(seperator_id) { try { - const config = JSON.parse(settings.global(Settings.FN_SEPARATOR_STATE(seperator_id), undefined)); + const config = JSON.parse(settings.getValue(Settings.FN_SEPARATOR_STATE(seperator_id), undefined)); if(config) { log.debug(LogCategory.GENERAL, tr("Applying previous changed sperator settings for %s: %o"), seperator_id, config); apply_view(config.property, config.previous, config.next); diff --git a/shared/js/ui/frames/chat.ts b/shared/js/ui/frames/chat.ts index 58b752fd..4f1b5e76 100644 --- a/shared/js/ui/frames/chat.ts +++ b/shared/js/ui/frames/chat.ts @@ -324,7 +324,7 @@ export function set_icon_size(size: string) { loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, { name: "icon size init", function: async () => { - set_icon_size((settings.static_global(Settings.KEY_ICON_SIZE) / 100).toFixed(2) + "em"); + set_icon_size((settings.getValue(Settings.KEY_ICON_SIZE) / 100).toFixed(2) + "em"); }, priority: 10 }); \ No newline at end of file diff --git a/shared/js/ui/frames/control-bar/Controller.ts b/shared/js/ui/frames/control-bar/Controller.ts index 0fc15285..72034f9f 100644 --- a/shared/js/ui/frames/control-bar/Controller.ts +++ b/shared/js/ui/frames/control-bar/Controller.ts @@ -167,7 +167,7 @@ class InfoController { public sendConnectionState() { const globallyConnected = server_connections.all_connections().findIndex(e => e.connected) !== -1; const locallyConnected = this.currentHandler?.connected; - const multisession = !settings.static_global(Settings.KEY_DISABLE_MULTI_SESSION); + const multisession = !settings.getValue(Settings.KEY_DISABLE_MULTI_SESSION); this.events.fire_react("notify_connection_state", { state: { @@ -349,8 +349,8 @@ export function initializeControlBarController(events: Registry !!e).forEach(connection => { connection.setAway(value); }); - settings.changeGlobal(Settings.KEY_CLIENT_STATE_AWAY, true); - settings.changeGlobal(Settings.KEY_CLIENT_AWAY_MESSAGE, typeof value === "boolean" ? "" : value); + settings.setValue(Settings.KEY_CLIENT_STATE_AWAY, true); + settings.setValue(Settings.KEY_CLIENT_AWAY_MESSAGE, typeof value === "boolean" ? "" : value); }; if(event.promptMessage) { @@ -368,13 +368,13 @@ export function initializeControlBarController(events: Registry { /* change the default global setting */ - settings.changeGlobal(Settings.KEY_CLIENT_STATE_MICROPHONE_MUTED, !event.enabled); + settings.setValue(Settings.KEY_CLIENT_STATE_MICROPHONE_MUTED, !event.enabled); const current_connection_handler = infoHandler.getCurrentHandler(); if(current_connection_handler) { @@ -391,19 +391,19 @@ export function initializeControlBarController(events: Registry { /* change the default global setting */ - settings.changeGlobal(Settings.KEY_CLIENT_STATE_SPEAKER_MUTED, !event.enabled); + settings.setValue(Settings.KEY_CLIENT_STATE_SPEAKER_MUTED, !event.enabled); infoHandler.getCurrentHandler()?.setSpeakerMuted(!event.enabled); }); events.on("action_toggle_subscribe", event => { - settings.changeGlobal(Settings.KEY_CLIENT_STATE_SUBSCRIBE_ALL_CHANNELS, event.subscribe); + settings.setValue(Settings.KEY_CLIENT_STATE_SUBSCRIBE_ALL_CHANNELS, event.subscribe); infoHandler.getCurrentHandler()?.setSubscribeToAllChannels(event.subscribe); }); events.on("action_toggle_query", event => { - settings.changeGlobal(Settings.KEY_CLIENT_STATE_QUERY_SHOWN, event.show); + settings.setValue(Settings.KEY_CLIENT_STATE_QUERY_SHOWN, event.show); infoHandler.getCurrentHandler()?.setQueriesShown(event.show); }); diff --git a/shared/js/ui/frames/control-bar/Renderer.tsx b/shared/js/ui/frames/control-bar/Renderer.tsx index af06134d..38ce7020 100644 --- a/shared/js/ui/frames/control-bar/Renderer.tsx +++ b/shared/js/ui/frames/control-bar/Renderer.tsx @@ -279,7 +279,7 @@ const VideoButton = (props: { type: VideoBroadcastType }) => { let dropdownText = props.type === "camera" ? tr("Start video broadcasting") : tr("Start screen sharing"); return (