diff --git a/github b/github index 3505268..90d3a6c 160000 --- a/github +++ b/github @@ -1 +1 @@ -Subproject commit 35052680fe58de01102dfd4b65835df08c2a7f20 +Subproject commit 90d3a6c84c003ab42a3ecfb5197b9368897862b1 diff --git a/imports/shared-app/Bookmarks.d.ts b/imports/shared-app/Bookmarks.d.ts index f0c7648..f333973 100644 --- a/imports/shared-app/Bookmarks.d.ts +++ b/imports/shared-app/Bookmarks.d.ts @@ -45,9 +45,9 @@ export declare class BookmarkManager { private readonly registeredBookmarks; private defaultBookmarkCreated; constructor(); - private loadBookmarks; + loadBookmarks(): Promise; private importOldBookmarks; - private saveBookmarks; + saveBookmarks(): Promise; getRegisteredBookmarks(): BookmarkEntry[]; getOrderedRegisteredBookmarks(): OrderedBookmarkEntry[]; findBookmark(uniqueId: string): BookmarkEntry | undefined; diff --git a/imports/shared-app/ConnectionHandler.d.ts b/imports/shared-app/ConnectionHandler.d.ts index 1132f6f..9b8a464 100644 --- a/imports/shared-app/ConnectionHandler.d.ts +++ b/imports/shared-app/ConnectionHandler.d.ts @@ -104,6 +104,7 @@ export declare class ConnectionHandler { settings: ServerSettings; sound: SoundManager; serverFeatures: ServerFeatures; + log: ServerEventLog; private sideBar; private playlistManager; private channelConversations; @@ -116,13 +117,12 @@ export declare class ConnectionHandler { private connectAttemptId; private echoTestRunning; private pluginCmdRegistry; - private client_status; + private handlerState; private clientStatusSync; private inputHardwareState; private listenerRecorderInputDeviceChanged; - log: ServerEventLog; constructor(); - initialize_client_state(source?: ConnectionHandler): void; + initializeHandlerState(source?: ConnectionHandler): void; events(): Registry; startConnectionNew(parameters: ConnectParameters, autoReconnectAttempt: boolean): Promise; startConnection(addr: string, profile: ConnectionProfile, user_action: boolean, parameters: ConnectParametersOld): Promise; @@ -142,7 +142,6 @@ export declare class ConnectionHandler { private _certificate_modal; handleDisconnect(type: DisconnectReason, data?: any): void; cancelAutoReconnect(log_event: boolean): void; - private on_connection_state_changed; private updateVoiceStatus; private lastRecordErrorPopup; update_voice_status(): void; @@ -156,7 +155,6 @@ export declare class ConnectionHandler { }>; getVoiceRecorder(): RecorderProfile | undefined; reconnect_properties(profile?: ConnectionProfile): ConnectParametersOld; - update_avatar(): void; private initializeWhisperSession; destroy(): void; setMicrophoneMuted(muted: boolean, dontPlaySound?: boolean): void; @@ -192,8 +190,5 @@ export interface ConnectionEvents { oldState: ConnectionState; newState: ConnectionState; }; - notify_visibility_changed: { - visible: boolean; - }; notify_handler_initialized: {}; } diff --git a/imports/shared-app/Mutex.d.ts b/imports/shared-app/Mutex.d.ts new file mode 100644 index 0000000..be206f2 --- /dev/null +++ b/imports/shared-app/Mutex.d.ts @@ -0,0 +1,18 @@ +export declare class Mutex { + private value; + private taskExecuting; + private taskQueue; + private freeListener; + constructor(value: T); + isFree(): boolean; + awaitFree(): Promise; + execute(callback: (value: T, setValue: (newValue: T) => void) => R | Promise): Promise; + tryExecute(callback: (value: T, setValue: (newValue: T) => void) => R | Promise): Promise<{ + status: "success"; + result: R; + } | { + status: "would-block"; + }>; + private executeNextTask; + private triggerFinished; +} diff --git a/imports/shared-app/PPTListener.d.ts b/imports/shared-app/PPTListener.d.ts index 9b6dc82..ae58f7a 100644 --- a/imports/shared-app/PPTListener.d.ts +++ b/imports/shared-app/PPTListener.d.ts @@ -1,122 +1,3 @@ -export declare enum KeyCode { - KEY_CANCEL = 3, - KEY_HELP = 6, - KEY_BACK_SPACE = 8, - KEY_TAB = 9, - KEY_CLEAR = 12, - KEY_RETURN = 13, - KEY_ENTER = 14, - KEY_SHIFT = 16, - KEY_CONTROL = 17, - KEY_ALT = 18, - KEY_PAUSE = 19, - KEY_CAPS_LOCK = 20, - KEY_ESCAPE = 27, - KEY_SPACE = 32, - KEY_PAGE_UP = 33, - KEY_PAGE_DOWN = 34, - KEY_END = 35, - KEY_HOME = 36, - KEY_LEFT = 37, - KEY_UP = 38, - KEY_RIGHT = 39, - KEY_DOWN = 40, - KEY_PRINTSCREEN = 44, - KEY_INSERT = 45, - KEY_DELETE = 46, - KEY_0 = 48, - KEY_1 = 49, - KEY_2 = 50, - KEY_3 = 51, - KEY_4 = 52, - KEY_5 = 53, - KEY_6 = 54, - KEY_7 = 55, - KEY_8 = 56, - KEY_9 = 57, - KEY_SEMICOLON = 59, - KEY_EQUALS = 61, - KEY_A = 65, - KEY_B = 66, - KEY_C = 67, - KEY_D = 68, - KEY_E = 69, - KEY_F = 70, - KEY_G = 71, - KEY_H = 72, - KEY_I = 73, - KEY_J = 74, - KEY_K = 75, - KEY_L = 76, - KEY_M = 77, - KEY_N = 78, - KEY_O = 79, - KEY_P = 80, - KEY_Q = 81, - KEY_R = 82, - KEY_S = 83, - KEY_T = 84, - KEY_U = 85, - KEY_V = 86, - KEY_W = 87, - KEY_X = 88, - KEY_Y = 89, - KEY_Z = 90, - KEY_LEFT_CMD = 91, - KEY_RIGHT_CMD = 93, - KEY_CONTEXT_MENU = 93, - KEY_NUMPAD0 = 96, - KEY_NUMPAD1 = 97, - KEY_NUMPAD2 = 98, - KEY_NUMPAD3 = 99, - KEY_NUMPAD4 = 100, - KEY_NUMPAD5 = 101, - KEY_NUMPAD6 = 102, - KEY_NUMPAD7 = 103, - KEY_NUMPAD8 = 104, - KEY_NUMPAD9 = 105, - KEY_MULTIPLY = 106, - KEY_ADD = 107, - KEY_SEPARATOR = 108, - KEY_SUBTRACT = 109, - KEY_DECIMAL = 110, - KEY_DIVIDE = 111, - KEY_F1 = 112, - KEY_F2 = 113, - KEY_F3 = 114, - KEY_F4 = 115, - KEY_F5 = 116, - KEY_F6 = 117, - KEY_F7 = 118, - KEY_F8 = 119, - KEY_F9 = 120, - KEY_F10 = 121, - KEY_F11 = 122, - KEY_F12 = 123, - KEY_F13 = 124, - KEY_F14 = 125, - KEY_F15 = 126, - KEY_F16 = 127, - KEY_F17 = 128, - KEY_F18 = 129, - KEY_F19 = 130, - KEY_F20 = 131, - KEY_F21 = 132, - KEY_F22 = 133, - KEY_F23 = 134, - KEY_F24 = 135, - KEY_NUM_LOCK = 144, - KEY_SCROLL_LOCK = 145, - KEY_COMMA = 188, - KEY_PERIOD = 190, - KEY_SLASH = 191, - KEY_BACK_QUOTE = 192, - KEY_OPEN_BRACKET = 219, - KEY_BACK_SLASH = 220, - KEY_CLOSE_BRACKET = 221, - KEY_QUOTE = 222, - KEY_META = 224 -} export declare enum EventType { KEY_PRESS = 0, KEY_RELEASE = 1, @@ -139,14 +20,17 @@ export interface KeyEvent extends KeyDescriptor { readonly type: EventType; readonly key: string; } -export interface KeyHook extends KeyDescriptor { +export interface KeyHook extends Partial { callbackPress: () => any; callbackRelease: () => any; } +interface RegisteredKeyHook extends KeyHook { + triggered: boolean; +} export interface KeyBoardBackend { registerListener(listener: (event: KeyEvent) => void): any; unregisterListener(listener: (event: KeyEvent) => void): any; - registerHook(hook: KeyHook): any; + registerHook(hook: KeyHook): () => void; unregisterHook(hook: KeyHook): any; isKeyPressed(key: string | SpecialKey): boolean; } @@ -156,18 +40,19 @@ export declare class AbstractKeyBoard implements KeyBoardBackend { [key: number]: boolean; }; protected readonly activeKeys: any; - protected registeredKeyHooks: KeyHook[]; - protected activeKeyHooks: KeyHook[]; + protected registeredKeyHooks: RegisteredKeyHook[]; constructor(); protected destroy(): void; isKeyPressed(key: string | SpecialKey): boolean; - registerHook(hook: KeyHook): void; + registerHook(hook: KeyHook): () => void; unregisterHook(hook: KeyHook): void; registerListener(listener: (event: KeyEvent) => void): void; unregisterListener(listener: (event: KeyEvent) => void): void; + private shouldHookBeActive; protected fireKeyEvent(event: KeyEvent): void; protected resetKeyboardState(): void; } export declare function getKeyBoard(): KeyBoardBackend; export declare function setKeyBoardBackend(newBackend: KeyBoardBackend): void; export declare function getKeyDescription(key: KeyDescriptor): string; +export {}; diff --git a/imports/shared-app/SelectedClientInfo.d.ts b/imports/shared-app/SelectedClientInfo.d.ts index 2ae56f6..23523e0 100644 --- a/imports/shared-app/SelectedClientInfo.d.ts +++ b/imports/shared-app/SelectedClientInfo.d.ts @@ -23,6 +23,7 @@ export declare type CachedClientInfo = { status: ClientStatusInfo; forumAccount: ClientForumInfo | undefined; channelGroup: number; + channelGroupInheritedChannel: number; serverGroups: number[]; version: ClientVersionInfo; }; @@ -52,5 +53,6 @@ export declare class SelectedClientInfo { private updateCachedCountry; private updateCachedVolume; private updateForumAccount; + private updateChannelGroup; private initializeClientInfo; } diff --git a/imports/shared-app/ServerSettings.d.ts b/imports/shared-app/ServerSettings.d.ts new file mode 100644 index 0000000..56f512b --- /dev/null +++ b/imports/shared-app/ServerSettings.d.ts @@ -0,0 +1,20 @@ +import { RegistryKey, RegistryValueType, ValuedRegistryKey } from "tc-shared/settings"; +export declare class ServerSettings { + private cacheServer; + private settingsDestroyed; + private serverUniqueId; + private serverSaveWorker; + private serverSettingsUpdated; + constructor(); + destroy(): void; + getValue(key: RegistryKey, defaultValue: DV): V | DV; + getValue(key: ValuedRegistryKey, defaultValue?: V): V; + setValue(key: RegistryKey, value?: T): void; + setServerUniqueId(serverUniqueId: string): void; + save(): void; +} +export interface ServerSettingsStorage { + get(serverUniqueId: string): string; + set(serverUniqueId: string, value: string): any; +} +export declare function setServerSettingsStorage(storage: ServerSettingsStorage): void; diff --git a/imports/shared-app/StorageAdapter.d.ts b/imports/shared-app/StorageAdapter.d.ts new file mode 100644 index 0000000..5cbfc5b --- /dev/null +++ b/imports/shared-app/StorageAdapter.d.ts @@ -0,0 +1,16 @@ +/** + * Application storage meant for small and medium large internal app states. + * Possible data would be non user editable cached values like auth tokens. + * Note: + * 1. Please consider using a Settings key first before using the storage adapter! + * 2. The values may not be synced across multiple window instances. + * Don't use this for IPC. + */ +export interface StorageAdapter { + has(key: string): Promise; + get(key: string): Promise; + set(key: string, value: string): Promise; + delete(key: string): Promise; +} +export declare function getStorageAdapter(): StorageAdapter; +export declare function setStorageAdapter(adapter: StorageAdapter): void; diff --git a/imports/shared-app/audio/Player.d.ts b/imports/shared-app/audio/Player.d.ts index af245e8..a9f31b2 100644 --- a/imports/shared-app/audio/Player.d.ts +++ b/imports/shared-app/audio/Player.d.ts @@ -3,6 +3,10 @@ export interface OutputDevice { driver: string; name: string; } +export declare namespace OutputDevice { + const NoDeviceId = "none"; + const DefaultDeviceId = "default"; +} export interface AudioBackendEvents { notify_initialized: {}; notify_volume_changed: { diff --git a/imports/shared-app/audio/Recorder.d.ts b/imports/shared-app/audio/Recorder.d.ts index 88cde76..cca052a 100644 --- a/imports/shared-app/audio/Recorder.d.ts +++ b/imports/shared-app/audio/Recorder.d.ts @@ -4,8 +4,6 @@ export interface AudioRecorderBacked { createInput(): AbstractInput; createLevelMeter(device: InputDevice): Promise; getDeviceList(): DeviceList; - isRnNoiseSupported(): boolean; - toggleRnNoise(target: boolean): any; } export interface DeviceListEvents { notify_list_updated: { diff --git a/imports/shared-app/audio/Sounds.d.ts b/imports/shared-app/audio/Sounds.d.ts index 2563dfb..7c8d1e2 100644 --- a/imports/shared-app/audio/Sounds.d.ts +++ b/imports/shared-app/audio/Sounds.d.ts @@ -59,13 +59,13 @@ export interface SoundFile { export declare function get_sound_volume(sound: Sound, default_volume?: number): number; export declare function set_sound_volume(sound: Sound, volume: number): void; export declare function get_master_volume(): number; -export declare function set_master_volume(volume: number): void; +export declare function setSoundMasterVolume(volume: number): void; export declare function overlap_activated(): boolean; export declare function set_overlap_activated(flag: boolean): void; export declare function ignore_output_muted(): boolean; export declare function set_ignore_output_muted(flag: boolean): void; export declare function save(): void; -export declare function initialize(): Promise; +export declare function initializeSounds(): Promise; export interface PlaybackOptions { ignore_muted?: boolean; ignore_overlap?: boolean; diff --git a/imports/shared-app/connection/ClientInfo.d.ts b/imports/shared-app/connection/ClientInfo.d.ts new file mode 100644 index 0000000..3eb7174 --- /dev/null +++ b/imports/shared-app/connection/ClientInfo.d.ts @@ -0,0 +1,25 @@ +import { ConnectionHandler } from "tc-shared/ConnectionHandler"; +export declare type ClientInfoResult = { + status: "success"; + clientName: string; + clientUniqueId: string; + clientDatabaseId: number; +} | { + status: "not-found"; +} | { + status: "error"; + error: string; +}; +export declare class ClientInfoResolver { + private readonly handler; + private readonly requestDatabaseIds; + private readonly requestUniqueIds; + private executed; + constructor(handler: ConnectionHandler); + private registerRequest; + private fullFullAllRequests; + private static parseClientInfo; + getInfoByDatabaseId(databaseId: number): Promise; + getInfoByUniqueId(uniqueId: string): Promise; + executeQueries(): Promise; +} diff --git a/imports/shared-app/connection/DummyVoiceConnection.d.ts b/imports/shared-app/connection/DummyVoiceConnection.d.ts index 40d6b1c..c51d8ad 100644 --- a/imports/shared-app/connection/DummyVoiceConnection.d.ts +++ b/imports/shared-app/connection/DummyVoiceConnection.d.ts @@ -6,6 +6,7 @@ import { WhisperSession, WhisperTarget } from "../voice/VoiceWhisper"; export declare class DummyVoiceConnection extends AbstractVoiceConnection { private recorder; private voiceClients; + private triggerUnmountEvent; constructor(connection: AbstractServerConnection); acquireVoiceRecorder(recorder: RecorderProfile | undefined): Promise; availableVoiceClients(): VoiceClient[]; diff --git a/imports/shared-app/connection/PluginCmdHandler.d.ts b/imports/shared-app/connection/PluginCmdHandler.d.ts index 82ce7b5..27e7136 100644 --- a/imports/shared-app/connection/PluginCmdHandler.d.ts +++ b/imports/shared-app/connection/PluginCmdHandler.d.ts @@ -14,7 +14,7 @@ export declare abstract class PluginCmdHandler { handleHandlerRegistered(): void; getChannel(): string; abstract handlePluginCommand(data: string, invoker: PluginCommandInvoker): any; - protected sendPluginCommand(data: string, mode: "server" | "view" | "channel" | "private", clientId?: number): Promise; + protected sendPluginCommand(data: string, mode: "server" | "view" | "channel" | "private", clientOrChannelId?: number): Promise; } export declare class PluginCmdRegistry { readonly connection: ConnectionHandler; diff --git a/imports/shared-app/connection/rtc/Connection.d.ts b/imports/shared-app/connection/rtc/Connection.d.ts index 1b5b44b..4e773d6 100644 --- a/imports/shared-app/connection/rtc/Connection.d.ts +++ b/imports/shared-app/connection/rtc/Connection.d.ts @@ -97,6 +97,7 @@ export declare class RTCConnection { restartConnection(): void; reset(updateConnectionState: boolean): void; setTrackSource(type: RTCSourceTrackType, source: MediaStreamTrack | null): Promise; + clearTrackSources(types: RTCSourceTrackType[]): Promise; startVideoBroadcast(type: VideoBroadcastType, config: VideoBroadcastConfig): Promise; changeVideoBroadcastConfig(type: VideoBroadcastType, config: VideoBroadcastConfig): Promise; startAudioBroadcast(): Promise; diff --git a/imports/shared-app/events/GlobalEvents.d.ts b/imports/shared-app/events/GlobalEvents.d.ts index e6ab2a1..bbfcf64 100644 --- a/imports/shared-app/events/GlobalEvents.d.ts +++ b/imports/shared-app/events/GlobalEvents.d.ts @@ -1,7 +1,7 @@ import { ConnectionHandler } from "../ConnectionHandler"; import { Registry } from "../events"; import { VideoBroadcastType } from "tc-shared/connection/VideoConnection"; -export declare type PermissionEditorTab = "groups-server" | "groups-channel" | "channel" | "client" | "client-channel"; +import { PermissionEditorTab } from "tc-shared/ui/modal/permission/ModalDefinitions"; export interface ClientGlobalControlEvents { action_open_window: { window: "settings" | /* use action_open_window_settings! */ "about" | "settings-registry" | "css-variable-editor" | "bookmark-manage" | "query-manage" | "query-create" | "ban-list" | "permissions" | "token-list" | "token-use" | "server-echo-test"; diff --git a/imports/shared-app/file/Avatars.d.ts b/imports/shared-app/file/Avatars.d.ts index 27e75cc..06a5ce0 100644 --- a/imports/shared-app/file/Avatars.d.ts +++ b/imports/shared-app/file/Avatars.d.ts @@ -38,7 +38,8 @@ export declare abstract class ClientAvatar { setLoading(): void; setLoaded(data: AvatarStateData["loaded"]): void; setErrored(data: AvatarStateData["errored"]): void; - awaitLoaded(): Promise; + awaitLoaded(): Promise; + awaitLoaded(timeout: number): Promise; getState(): AvatarState; getStateData(): AvatarStateData[AvatarState]; getAvatarHash(): string | "unknown"; diff --git a/imports/shared-app/file/ImageCache.d.ts b/imports/shared-app/file/ImageCache.d.ts index 2462308..b85ee13 100644 --- a/imports/shared-app/file/ImageCache.d.ts +++ b/imports/shared-app/file/ImageCache.d.ts @@ -6,7 +6,7 @@ export declare enum ImageType { SVG = 4, JPEG = 5 } -export declare function imageType2MediaType(type: ImageType, file?: boolean): "svg" | "jpeg" | "png" | "bmp" | "gif" | "svg+xml"; +export declare function imageType2MediaType(type: ImageType, file?: boolean): "svg" | "bmp" | "gif" | "svg+xml" | "jpeg" | "png"; export declare function responseImageType(encoded_data: string | ArrayBuffer, base64_encoded?: boolean): ImageType; export declare type ImageCacheState = { state: "loaded"; diff --git a/imports/shared-app/file/OwnAvatarStorage.d.ts b/imports/shared-app/file/OwnAvatarStorage.d.ts new file mode 100644 index 0000000..65cdf8c --- /dev/null +++ b/imports/shared-app/file/OwnAvatarStorage.d.ts @@ -0,0 +1,43 @@ +export declare type LocalAvatarInfo = { + fileName: string; + fileSize: number; + fileHashMD5: string; + fileUploaded: number; + fileModified: number; + contentType: string; + resourceUrl: string | undefined; +}; +export declare type LocalAvatarUpdateResult = { + status: "success"; +} | { + status: "error"; + reason: string; +} | { + status: "cache-unavailable"; +}; +export declare type LocalAvatarLoadResult = { + status: "success"; + result: T; +} | { + status: "error"; + reason: string; +} | { + status: "cache-unavailable" | "empty-result"; +}; +export declare type OwnAvatarMode = "uploading" | "server"; +export declare class OwnAvatarStorage { + private openedCache; + private static generateRequestUrl; + initialize(): Promise; + private loadAvatarRequest; + loadAvatarImage(serverUniqueId: string, mode: OwnAvatarMode): Promise>; + loadAvatar(serverUniqueId: string, mode: OwnAvatarMode, createResourceUrl: boolean): Promise>; + updateAvatar(serverUniqueId: string, mode: OwnAvatarMode, target: File): Promise; + removeAvatar(serverUniqueId: string, mode: OwnAvatarMode): Promise; + /** + * Move the avatar file which is currently in "uploading" state to server + * @param serverUniqueId + */ + avatarUploadSucceeded(serverUniqueId: string): Promise; +} +export declare function getOwnAvatarStorage(): OwnAvatarStorage; diff --git a/imports/shared-app/file/Utils.d.ts b/imports/shared-app/file/Utils.d.ts index f4249dd..d504ef4 100644 --- a/imports/shared-app/file/Utils.d.ts +++ b/imports/shared-app/file/Utils.d.ts @@ -1,2 +1,6 @@ export declare const downloadTextAsFile: (text: string, name: string) => void; +export declare const requestFile: (options: { + accept?: string; + multiple?: boolean; +}) => Promise; export declare const requestFileAsText: () => Promise; diff --git a/imports/shared-app/i18n/country.d.ts b/imports/shared-app/i18n/country.d.ts index 738bc01..aeebf24 100644 --- a/imports/shared-app/i18n/country.d.ts +++ b/imports/shared-app/i18n/country.d.ts @@ -1 +1,14 @@ -export declare function country_name(alpha_code: string, fallback?: string): string; +import "svg-sprites/country-flags"; +import { CountryFlag } from "svg-sprites/country-flags"; +interface CountryInfo { + name: string; + alpha_2: string; + alpha_3: string; + un_code: number; + icon: string; + flagMissingWarned?: boolean; +} +export declare function getKnownCountries(): CountryInfo[]; +export declare function getCountryName(alphaCode: string, fallback?: string): string; +export declare function getCountryFlag(alphaCode: string): CountryFlag; +export {}; diff --git a/imports/shared-app/main.d.ts b/imports/shared-app/main.d.ts index 1af742a..d419f91 100644 --- a/imports/shared-app/main.d.ts +++ b/imports/shared-app/main.d.ts @@ -4,7 +4,6 @@ import { ConnectRequestData } from "tc-shared/ipc/ConnectHandler"; import "svg-sprites/client-icons"; import "../css/load-css"; import "./proto"; -import "./video-viewer/Controller"; import "./profiles/ConnectionProfile"; import "./update/UpdaterWeb"; import "./file/LocalIcons"; @@ -17,6 +16,8 @@ import "./media/Video"; import "./ui/AppController"; import "./ui/frames/menu-bar/MainMenu"; import "./ui/modal/connect/Controller"; +import "./ui/modal/video-viewer/Controller"; +import "./ui/modal/avatar-upload/Controller"; import "./ui/elements/ContextDivider"; import "./ui/elements/Tab"; import "./clientservice"; diff --git a/imports/shared-app/proto.d.ts b/imports/shared-app/proto.d.ts index 22d192e..dc8e767 100644 --- a/imports/shared-app/proto.d.ts +++ b/imports/shared-app/proto.d.ts @@ -72,3 +72,12 @@ export declare type ReadonlyKeys = { -readonly [Q in P]: T[P]; }, never, P>; }[keyof T]; +export declare function crashOnThrow(promise: Promise | (() => Promise)): Promise; +export declare function ignorePromise(_promise: Promise): void; +export declare function NoThrow(target: any, methodName: string, descriptor: PropertyDescriptor): void; +export declare function CallOnce(target: any, methodName: string, descriptor: PropertyDescriptor): void; +export declare function NonNull(target: any, methodName: string, parameterIndex: number): void; +/** + * The class or method has been constrained + */ +export declare function ParameterConstrained(target: any, methodName: string, descriptor: PropertyDescriptor): void; diff --git a/imports/shared-app/settings.d.ts b/imports/shared-app/settings.d.ts index 54694c1..4286edf 100644 --- a/imports/shared-app/settings.d.ts +++ b/imports/shared-app/settings.d.ts @@ -15,6 +15,8 @@ export interface RegistryKey { export interface ValuedRegistryKey extends RegistryKey { defaultValue: ValueType; } +export declare function encodeSettingValueToString(input: T): string; +export declare function resolveSettingKey(key: RegistryKey, resolver: (key: string) => string | undefined | null, defaultValue: DefaultType): ValueType | DefaultType; export declare class UrlParameterParser { private readonly url; constructor(url: URL); @@ -53,14 +55,6 @@ export declare namespace AppParameters { const KEY_MODAL_IPC_CHANNEL: RegistryKey; const KEY_LOAD_DUMMY_ERROR: ValuedRegistryKey; } -export declare class StaticSettings { - private static _instance; - static get instance(): StaticSettings; - protected staticValues: {}; - protected constructor(_reserved?: any); - static(key: RegistryKey, defaultValue: DV): V | DV; - static(key: ValuedRegistryKey, defaultValue?: V): V; -} export interface SettingsEvents { notify_setting_changed: { setting: string; @@ -145,6 +139,9 @@ export declare class Settings { static readonly KEY_MICROPHONE_THRESHOLD_ATTACK_SMOOTH: ValuedRegistryKey; static readonly KEY_MICROPHONE_THRESHOLD_RELEASE_SMOOTH: ValuedRegistryKey; static readonly KEY_MICROPHONE_THRESHOLD_RELEASE_DELAY: ValuedRegistryKey; + static readonly KEY_SPEAKER_DEVICE_ID: RegistryKey; + static readonly KEY_UPDATER_LAST_USED_UI: RegistryKey; + static readonly KEY_UPDATER_LAST_USED_CLIENT: RegistryKey; static readonly FN_LOG_ENABLED: (category: string) => RegistryKey; static readonly FN_SEPARATOR_STATE: (separator: string) => RegistryKey; static readonly FN_LOG_LEVEL_ENABLED: (category: string) => RegistryKey; @@ -159,30 +156,18 @@ export declare class Settings { static readonly FN_EVENTS_LOG_ENABLED: (event: string) => RegistryKey; static readonly FN_EVENTS_FOCUS_ENABLED: (event: string) => RegistryKey; static readonly KEYS: any[]; - static initialize(): void; readonly events: Registry; - private readonly cacheGlobal; + private settingsCache; private saveWorker; private updated; + private saveState; constructor(); + initialize(): Promise; getValue(key: RegistryKey, defaultValue: DV): V | DV; getValue(key: ValuedRegistryKey, defaultValue?: V): V; setValue(key: RegistryKey, value?: T): void; globalChangeListener(key: RegistryKey, listener: (newValue: T) => void): () => void; - save(): void; -} -export declare class ServerSettings { - private cacheServer; - private serverUniqueId; - private serverSaveWorker; - private serverSettingsUpdated; - private _destroyed; - constructor(); - destroy(): void; - getValue(key: RegistryKey, defaultValue: DV): V | DV; - getValue(key: ValuedRegistryKey, defaultValue?: V): V; - setValue(key: RegistryKey, value?: T): void; - setServer(server_unique_id: string): void; + private doSave; save(): void; } export declare let settings: Settings; diff --git a/imports/shared-app/text/bbcode/renderer.d.ts b/imports/shared-app/text/bbcode/renderer.d.ts index bc5d8aa..5105a85 100644 --- a/imports/shared-app/text/bbcode/renderer.d.ts +++ b/imports/shared-app/text/bbcode/renderer.d.ts @@ -4,7 +4,7 @@ import ReactRenderer from "vendor/xbbcode/renderer/react"; import HTMLRenderer from "vendor/xbbcode/renderer/html"; import "./emoji"; import "./highlight"; -import "./YoutubeRenderer"; +import "./YoutubeController"; import "./url"; import "./image"; export declare let BBCodeHandlerContext: Context; diff --git a/imports/shared-app/tree/Client.d.ts b/imports/shared-app/tree/Client.d.ts index 010e402..65729b4 100644 --- a/imports/shared-app/tree/Client.d.ts +++ b/imports/shared-app/tree/Client.d.ts @@ -100,9 +100,7 @@ export declare class ClientConnectionInfo { } export interface ClientEvents extends ChannelTreeEntryEvents { notify_properties_updated: { - updated_properties: { - [Key in keyof ClientProperties]: ClientProperties[Key]; - }; + updated_properties: Partial; client_properties: ClientProperties; }; notify_mute_state_change: { @@ -166,8 +164,10 @@ export declare class ClientEntry ext static chatTag(id: number, name: string, uid: string, braces?: boolean): JQuery; create_bbcode(): string; createChatTag(braces?: boolean): JQuery; - set speaking(flag: any); + /** @deprecated Don't use this any more! */ + set speaking(flag: boolean); isSpeaking(): boolean; + protected setSpeaking(flag: boolean): void; updateVariables(...variables: { key: string; value: string; @@ -188,6 +188,7 @@ export declare class ClientEntry ext export declare class LocalClientEntry extends ClientEntry { handle: ConnectionHandler; constructor(handle: ConnectionHandler); + setSpeaking(flag: boolean): void; showContextMenu(x: number, y: number, on_close?: () => void): void; renameSelf(new_name: string): Promise; openRenameModal(): void; diff --git a/imports/shared-app/ui/frames/control-bar/Controller.d.ts b/imports/shared-app/ui/frames/control-bar/Controller.d.ts index 12b5fbf..524c381 100644 --- a/imports/shared-app/ui/frames/control-bar/Controller.d.ts +++ b/imports/shared-app/ui/frames/control-bar/Controller.d.ts @@ -28,6 +28,7 @@ declare class InfoController { sendMicrophoneState(): void; sendMicrophoneList(): void; sendSpeakerState(): void; + sendSpeakerList(): Promise; sendSubscribeState(): void; sendQueryState(): void; sendHostButton(): void; diff --git a/imports/shared-app/ui/frames/control-bar/Definitions.d.ts b/imports/shared-app/ui/frames/control-bar/Definitions.d.ts index 3ffd6fc..aaa2b13 100644 --- a/imports/shared-app/ui/frames/control-bar/Definitions.d.ts +++ b/imports/shared-app/ui/frames/control-bar/Definitions.d.ts @@ -27,7 +27,7 @@ export declare type VideoDeviceInfo = { name: string; id: string; }; -export declare type MicrophoneDeviceInfo = { +export declare type AudioDeviceInfo = { name: string; id: string; driver: string; @@ -57,6 +57,7 @@ export interface ControlBarEvents { }; action_toggle_speaker: { enabled: boolean; + targetDeviceId?: string; }; action_toggle_subscribe: { subscribe: boolean; @@ -75,6 +76,7 @@ export interface ControlBarEvents { broadcastType: VideoBroadcastType; }; action_open_microphone_settings: {}; + action_open_speaker_settings: {}; query_mode: {}; query_connection_state: {}; query_bookmarks: {}; @@ -82,6 +84,7 @@ export interface ControlBarEvents { query_microphone_state: {}; query_microphone_list: {}; query_speaker_state: {}; + query_speaker_list: {}; query_subscribe_state: {}; query_query_state: {}; query_host_button: {}; @@ -105,11 +108,17 @@ export interface ControlBarEvents { state: MicrophoneState; }; notify_microphone_list: { - devices: MicrophoneDeviceInfo[]; + devices: AudioDeviceInfo[]; }; notify_speaker_state: { enabled: boolean; }; + notify_speaker_list: { + state: "initialized"; + devices: AudioDeviceInfo[]; + } | { + state: "uninitialized"; + }; notify_subscribe_state: { subscribe: boolean; }; diff --git a/imports/shared-app/ui/frames/control-bar/DropDown.d.ts b/imports/shared-app/ui/frames/control-bar/DropDown.d.ts index 3d0a0ff..6fd27a2 100644 --- a/imports/shared-app/ui/frames/control-bar/DropDown.d.ts +++ b/imports/shared-app/ui/frames/control-bar/DropDown.d.ts @@ -1,5 +1,4 @@ import * as React from "react"; -import { ReactComponentBase } from "tc-shared/ui/react-elements/ReactComponentBase"; import { RemoteIconInfo } from "tc-shared/file/Icons"; export interface DropdownEntryProperties { icon?: string | RemoteIconInfo; @@ -7,10 +6,14 @@ export interface DropdownEntryProperties { onClick?: (event: React.MouseEvent) => void; onAuxClick?: (event: React.MouseEvent) => void; onContextMenu?: (event: React.MouseEvent) => void; - children?: React.ReactElement[]; + children?: React.ReactElement[]; } -export declare class DropdownEntry extends ReactComponentBase { - protected defaultState(): {}; +export declare class DropdownEntry extends React.PureComponent { + render(): JSX.Element; +} +export declare class DropdownTitleEntry extends React.PureComponent<{ + children: any; +}> { render(): JSX.Element; } export declare const DropdownContainer: (props: { diff --git a/imports/shared-app/ui/frames/side/AbstractConversationController.d.ts b/imports/shared-app/ui/frames/side/AbstractConversationController.d.ts index 5b6a195..6048a9f 100644 --- a/imports/shared-app/ui/frames/side/AbstractConversationController.d.ts +++ b/imports/shared-app/ui/frames/side/AbstractConversationController.d.ts @@ -15,7 +15,6 @@ export declare abstract class AbstractConversationController): void; uiQueryHistory(conversation: AbstractChat, timestamp: number, enforce?: boolean): void; protected getCurrentConversation(): ConversationType | undefined; diff --git a/imports/shared-app/ui/frames/side/AbstractConversationDefinitions.d.ts b/imports/shared-app/ui/frames/side/AbstractConversationDefinitions.d.ts index 8cb63d3..aa468e9 100644 --- a/imports/shared-app/ui/frames/side/AbstractConversationDefinitions.d.ts +++ b/imports/shared-app/ui/frames/side/AbstractConversationDefinitions.d.ts @@ -121,7 +121,6 @@ export interface AbstractConversationUiEvents { notify_selected_chat: { chatId: "unselected" | string; }; - notify_panel_show: {}; notify_chat_event: { chatId: string; triggerUnread: boolean; diff --git a/imports/shared-app/ui/frames/side/ChannelConversationController.d.ts b/imports/shared-app/ui/frames/side/ChannelConversationController.d.ts index a890091..8300f95 100644 --- a/imports/shared-app/ui/frames/side/ChannelConversationController.d.ts +++ b/imports/shared-app/ui/frames/side/ChannelConversationController.d.ts @@ -8,7 +8,6 @@ export declare class ChannelConversationController extends AbstractConversationC constructor(); destroy(): void; setConnectionHandler(connection: ConnectionHandler): void; - private initializeConnectionListener; private handleMessageDelete; protected registerConversationEvents(conversation: ChannelConversation): void; } diff --git a/imports/shared-app/ui/frames/side/ClientInfoController.d.ts b/imports/shared-app/ui/frames/side/ClientInfoController.d.ts index 6c2e834..b2a483a 100644 --- a/imports/shared-app/ui/frames/side/ClientInfoController.d.ts +++ b/imports/shared-app/ui/frames/side/ClientInfoController.d.ts @@ -3,10 +3,13 @@ export declare class ClientInfoController { private readonly uiEvents; private connection; private listenerConnection; + private listenerInheritedChannel; + private inheritedChannelInfo; constructor(); destroy(): void; setConnectionHandler(connection: ConnectionHandler): void; private initializeConnection; + private updateInheritedInfo; private generateGroupInfo; private sendClient; private sendChannelGroup; diff --git a/imports/shared-app/ui/frames/side/ClientInfoDefinitions.d.ts b/imports/shared-app/ui/frames/side/ClientInfoDefinitions.d.ts index 10f9693..db972a1 100644 --- a/imports/shared-app/ui/frames/side/ClientInfoDefinitions.d.ts +++ b/imports/shared-app/ui/frames/side/ClientInfoDefinitions.d.ts @@ -46,6 +46,10 @@ export declare type ClientVersionInfo = { platform: string; version: string; }; +export declare type InheritedChannelInfo = { + channelId: number; + channelName: string; +}; export interface ClientInfoEvents { action_show_full_info: {}; action_edit_avatar: {}; @@ -68,6 +72,7 @@ export interface ClientInfoEvents { }; notify_channel_group: { group: ClientGroupInfo | undefined; + inheritedChannel: InheritedChannelInfo | undefined; }; notify_server_groups: { groups: ClientGroupInfo[]; diff --git a/imports/shared-app/ui/frames/side/PrivateConversationController.d.ts b/imports/shared-app/ui/frames/side/PrivateConversationController.d.ts index 63d128b..60cc19d 100644 --- a/imports/shared-app/ui/frames/side/PrivateConversationController.d.ts +++ b/imports/shared-app/ui/frames/side/PrivateConversationController.d.ts @@ -15,7 +15,6 @@ export declare class PrivateConversationController extends AbstractConversationC constructor(); destroy(): void; setConnectionHandler(connection: ConnectionHandler): void; - private initializeConnectionListener; protected registerConversationManagerEvents(manager: PrivateConversationManager): void; focusInput(): void; private reportConversationList; diff --git a/imports/shared-app/ui/modal/ModalAvatar.d.ts b/imports/shared-app/ui/modal/ModalAvatar.d.ts deleted file mode 100644 index b85567a..0000000 --- a/imports/shared-app/ui/modal/ModalAvatar.d.ts +++ /dev/null @@ -1 +0,0 @@ -export declare function spawnAvatarUpload(callback_data: (data: ArrayBuffer | undefined | null) => any): void; diff --git a/imports/shared-app/ui/modal/ModalConnect.d.ts b/imports/shared-app/ui/modal/ModalConnect.d.ts deleted file mode 100644 index 9cb40af..0000000 --- a/imports/shared-app/ui/modal/ModalConnect.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -export declare const Regex: { - DOMAIN: RegExp; - IP_V4: RegExp; - IP_V6: RegExp; - IP: RegExp; -}; diff --git a/imports/shared-app/ui/modal/ModalGroupAssignment.d.ts b/imports/shared-app/ui/modal/ModalGroupAssignment.d.ts deleted file mode 100644 index 28762ca..0000000 --- a/imports/shared-app/ui/modal/ModalGroupAssignment.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -import { ClientEntry } from "../../tree/Client"; -export declare function createServerGroupAssignmentModal(client: ClientEntry, callback: (groups: number[], flag: boolean) => Promise): void; diff --git a/imports/shared-app/ui/modal/about/Controller.d.ts b/imports/shared-app/ui/modal/about/Controller.d.ts new file mode 100644 index 0000000..1a20ea3 --- /dev/null +++ b/imports/shared-app/ui/modal/about/Controller.d.ts @@ -0,0 +1 @@ +export declare function spawnAboutModal(): void; diff --git a/imports/shared-app/ui/modal/about/Definitions.d.ts b/imports/shared-app/ui/modal/about/Definitions.d.ts new file mode 100644 index 0000000..3dd07a4 --- /dev/null +++ b/imports/shared-app/ui/modal/about/Definitions.d.ts @@ -0,0 +1,15 @@ +export interface ModalAboutVariables { + readonly uiVersion: string; + readonly uiVersionTimestamp: number; + readonly nativeVersion: string; + eggShown: boolean; +} +export interface ModalAboutEvents { + action_update_high_score: { + score: number; + }; + query_high_score: {}; + notify_high_score: { + score: number; + }; +} diff --git a/imports/shared-app/ui/modal/about/Renderer.d.ts b/imports/shared-app/ui/modal/about/Renderer.d.ts new file mode 100644 index 0000000..455ec36 --- /dev/null +++ b/imports/shared-app/ui/modal/about/Renderer.d.ts @@ -0,0 +1,13 @@ +import { AbstractModal } from "tc-shared/ui/react-elements/modal/Definitions"; +import React from "react"; +import { IpcRegistryDescription } from "tc-events"; +import { ModalAboutEvents, ModalAboutVariables } from "tc-shared/ui/modal/about/Definitions"; +import { IpcVariableDescriptor } from "tc-shared/ui/utils/IpcVariable"; +declare class Modal extends AbstractModal { + private readonly events; + private readonly variables; + constructor(events: IpcRegistryDescription, variables: IpcVariableDescriptor); + renderBody(): React.ReactElement; + renderTitle(): string | React.ReactElement; +} +export default Modal; diff --git a/imports/shared-app/ui/modal/avatar-upload/Controller.d.ts b/imports/shared-app/ui/modal/avatar-upload/Controller.d.ts new file mode 100644 index 0000000..967037e --- /dev/null +++ b/imports/shared-app/ui/modal/avatar-upload/Controller.d.ts @@ -0,0 +1,2 @@ +import { ConnectionHandler } from "tc-shared/ConnectionHandler"; +export declare function spawnAvatarUpload(connection: ConnectionHandler): void; diff --git a/imports/shared-app/ui/modal/avatar-upload/Definitions.d.ts b/imports/shared-app/ui/modal/avatar-upload/Definitions.d.ts new file mode 100644 index 0000000..a49806f --- /dev/null +++ b/imports/shared-app/ui/modal/avatar-upload/Definitions.d.ts @@ -0,0 +1,33 @@ +export declare type CurrentAvatarState = { + status: "unset" | "loading"; +} | { + status: "available" | "exceeds-max-size"; + fileName: string; + fileSize: number; + fileHashMD5: string; + resourceUrl: string | undefined; + serverHasAvatar: boolean; +} | { + status: "server"; + resourceUrl: string; +}; +export interface ModalAvatarUploadVariables { + readonly maxAvatarSize: number; + readonly currentAvatar: CurrentAvatarState; +} +export interface ModalAvatarUploadEvents { + action_open_select: {}; + action_file_cache_loading: {}; + action_file_cache_loading_finished: { + success: boolean; + }; + action_avatar_upload: { + closeWindow: boolean; + }; + action_avatar_delete: { + closeWindow: boolean; + }; + notify_avatar_load_error: { + error: string; + }; +} diff --git a/imports/shared-app/ui/modal/avatar-upload/Renderer.d.ts b/imports/shared-app/ui/modal/avatar-upload/Renderer.d.ts new file mode 100644 index 0000000..2ce215f --- /dev/null +++ b/imports/shared-app/ui/modal/avatar-upload/Renderer.d.ts @@ -0,0 +1,15 @@ +import { AbstractModal } from "tc-shared/ui/react-elements/modal/Definitions"; +import React from "react"; +import { IpcRegistryDescription } from "tc-events"; +import { ModalAvatarUploadEvents, ModalAvatarUploadVariables } from "tc-shared/ui/modal/avatar-upload/Definitions"; +import { IpcVariableDescriptor } from "tc-shared/ui/utils/IpcVariable"; +declare class ModalAvatarUpload extends AbstractModal { + private readonly serverUniqueId; + private readonly events; + private readonly variables; + constructor(events: IpcRegistryDescription, variables: IpcVariableDescriptor, serverUniqueId: string); + protected onDestroy(): void; + renderBody(): React.ReactElement; + renderTitle(): string | React.ReactElement; +} +export default ModalAvatarUpload; diff --git a/imports/shared-app/ui/modal/group-assignment/Controller.d.ts b/imports/shared-app/ui/modal/group-assignment/Controller.d.ts new file mode 100644 index 0000000..98544e5 --- /dev/null +++ b/imports/shared-app/ui/modal/group-assignment/Controller.d.ts @@ -0,0 +1,2 @@ +import { ConnectionHandler } from "tc-shared/ConnectionHandler"; +export declare function spawnServerGroupAssignments(handler: ConnectionHandler, targetClientDatabaseId: number): void; diff --git a/imports/shared-app/ui/modal/group-assignment/Definitions.d.ts b/imports/shared-app/ui/modal/group-assignment/Definitions.d.ts new file mode 100644 index 0000000..52733eb --- /dev/null +++ b/imports/shared-app/ui/modal/group-assignment/Definitions.d.ts @@ -0,0 +1,57 @@ +import { RemoteIconInfo } from "tc-shared/file/Icons"; +export declare type AvailableGroup = { + groupId: number; + saveDB: boolean; + name: string; + icon: RemoteIconInfo | undefined; + addAble: boolean; + removeAble: boolean; +}; +export declare type ClientInfo = { + status: "success"; + clientDatabaseId: number; + clientUniqueId: string; + clientName: string; +} | { + status: "error"; + message: string; +}; +export interface ModalClientGroupAssignmentVariables { + readonly handlerId: string; + readonly targetClient: ClientInfo; + readonly availableGroups: { + groups: AvailableGroup[]; + defaultGroup: number; + }; + readonly assignedGroupStatus: { + status: "loaded"; + assignedGroups: number; + } | { + status: "loading"; + } | { + status: "error"; + message: string; + }; + groupAssigned: boolean; +} +export interface ModalClientGroupAssignmentEvents { + action_close: {}; + action_remove_all: {}; + action_refresh: { + slowMode: boolean; + }; + notify_toggle_result: { + action: "add" | "remove"; + groupId: number; + groupName: string; + result: { + status: "success"; + } | { + status: "error"; + reason: string; + } | { + status: "no-permissions"; + permission: string; + }; + }; +} diff --git a/imports/shared-app/ui/modal/group-assignment/Renderer.d.ts b/imports/shared-app/ui/modal/group-assignment/Renderer.d.ts new file mode 100644 index 0000000..70cae18 --- /dev/null +++ b/imports/shared-app/ui/modal/group-assignment/Renderer.d.ts @@ -0,0 +1,13 @@ +import { AbstractModal } from "tc-shared/ui/react-elements/modal/Definitions"; +import React from "react"; +import { IpcRegistryDescription } from "tc-events"; +import { ModalClientGroupAssignmentEvents, ModalClientGroupAssignmentVariables } from "tc-shared/ui/modal/group-assignment/Definitions"; +import { IpcVariableDescriptor } from "tc-shared/ui/utils/IpcVariable"; +export default class ModalServerGroups extends AbstractModal { + private readonly events; + private readonly variables; + constructor(events: IpcRegistryDescription, variables: IpcVariableDescriptor); + protected onDestroy(): void; + renderBody(): React.ReactElement; + renderTitle(): string | React.ReactElement; +} diff --git a/imports/shared-app/ui/modal/input-processor/Controller.d.ts b/imports/shared-app/ui/modal/input-processor/Controller.d.ts new file mode 100644 index 0000000..54a71a6 --- /dev/null +++ b/imports/shared-app/ui/modal/input-processor/Controller.d.ts @@ -0,0 +1,2 @@ +import { InputProcessor } from "tc-shared/voice/RecorderBase"; +export declare function spawnInputProcessorModal(processor: InputProcessor): void; diff --git a/imports/shared-app/ui/modal/input-processor/Definitios.d.ts b/imports/shared-app/ui/modal/input-processor/Definitios.d.ts new file mode 100644 index 0000000..1048ddd --- /dev/null +++ b/imports/shared-app/ui/modal/input-processor/Definitios.d.ts @@ -0,0 +1,13 @@ +import { InputProcessorConfigRNNoise, InputProcessorConfigWebRTC, InputProcessorStatistics } from "tc-shared/voice/RecorderBase"; +export declare type ModalInputProcessorVariables = { + propertyFilter: string; +} & InputProcessorConfigRNNoise & InputProcessorConfigWebRTC; +export interface ModalInputProcessorEvents { + query_statistics: {}; + notify_statistics: { + statistics: InputProcessorStatistics; + }; + notify_apply_error: { + message: string; + }; +} diff --git a/imports/shared-app/ui/modal/input-processor/Renderer.d.ts b/imports/shared-app/ui/modal/input-processor/Renderer.d.ts new file mode 100644 index 0000000..d102958 --- /dev/null +++ b/imports/shared-app/ui/modal/input-processor/Renderer.d.ts @@ -0,0 +1,14 @@ +import { AbstractModal } from "tc-shared/ui/react-elements/modal/Definitions"; +import React from "react"; +import { IpcRegistryDescription } from "tc-events"; +import { ModalInputProcessorEvents, ModalInputProcessorVariables } from "tc-shared/ui/modal/input-processor/Definitios"; +import { IpcVariableDescriptor } from "tc-shared/ui/utils/IpcVariable"; +declare class Modal extends AbstractModal { + private readonly events; + private readonly variables; + constructor(events: IpcRegistryDescription, variables: IpcVariableDescriptor); + protected onDestroy(): void; + renderBody(): React.ReactElement; + renderTitle(): string | React.ReactElement; +} +export default Modal; diff --git a/imports/shared-app/ui/modal/permission/PermissionEditor.d.ts b/imports/shared-app/ui/modal/permission/EditorDefinitions.d.ts similarity index 80% rename from imports/shared-app/ui/modal/permission/PermissionEditor.d.ts rename to imports/shared-app/ui/modal/permission/EditorDefinitions.d.ts index bb6aa91..d6bc2b7 100644 --- a/imports/shared-app/ui/modal/permission/PermissionEditor.d.ts +++ b/imports/shared-app/ui/modal/permission/EditorDefinitions.d.ts @@ -1,6 +1,3 @@ -import * as React from "react"; -import { Registry } from "tc-shared/events"; -import { ConnectionHandler } from "tc-shared/ConnectionHandler"; export interface EditorGroupedPermissions { groupId: string; groupName: string; @@ -11,7 +8,7 @@ export interface EditorGroupedPermissions { }[]; children: EditorGroupedPermissions[]; } -declare type PermissionEditorMode = "unset" | "no-permissions" | "normal"; +export declare type PermissionEditorMode = "unset" | "no-permissions" | "normal"; export interface PermissionEditorEvents { action_set_mode: { mode: PermissionEditorMode; @@ -104,15 +101,3 @@ export interface PermissionEditorEvents { }[]; }; } -interface PermissionEditorProperties { - connection: ConnectionHandler; - events: Registry; -} -interface PermissionEditorState { - state: "no-permissions" | "unset" | "normal"; -} -export declare class PermissionEditor extends React.Component { - render(): JSX.Element[]; - componentDidMount(): void; -} -export {}; diff --git a/imports/shared-app/ui/modal/permission/EditorRenderer.d.ts b/imports/shared-app/ui/modal/permission/EditorRenderer.d.ts new file mode 100644 index 0000000..f23fe9a --- /dev/null +++ b/imports/shared-app/ui/modal/permission/EditorRenderer.d.ts @@ -0,0 +1,16 @@ +import * as React from "react"; +import { Registry } from "tc-shared/events"; +import { PermissionEditorEvents } from "tc-shared/ui/modal/permission/EditorDefinitions"; +interface PermissionEditorProperties { + handlerId: string; + serverUniqueId: string; + events: Registry; +} +interface PermissionEditorState { + state: "no-permissions" | "unset" | "normal"; +} +export declare class EditorRenderer extends React.Component { + render(): JSX.Element; + componentDidMount(): void; +} +export {}; diff --git a/imports/shared-app/ui/modal/permission/ModalController.d.ts b/imports/shared-app/ui/modal/permission/ModalController.d.ts new file mode 100644 index 0000000..76e5c0d --- /dev/null +++ b/imports/shared-app/ui/modal/permission/ModalController.d.ts @@ -0,0 +1,4 @@ +import { ConnectionHandler } from "tc-shared/ConnectionHandler"; +import { DefaultTabValues } from "tc-shared/ui/modal/permission/ModalRenderer"; +import { PermissionEditorTab } from "tc-shared/ui/modal/permission/ModalDefinitions"; +export declare function spawnPermissionEditorModal(connection: ConnectionHandler, defaultTab?: PermissionEditorTab, defaultTabValues?: DefaultTabValues): void; diff --git a/imports/shared-app/ui/modal/permission/ModalPermissionEditor.d.ts b/imports/shared-app/ui/modal/permission/ModalDefinitions.d.ts similarity index 88% rename from imports/shared-app/ui/modal/permission/ModalPermissionEditor.d.ts rename to imports/shared-app/ui/modal/permission/ModalDefinitions.d.ts index e9d2925..de09f31 100644 --- a/imports/shared-app/ui/modal/permission/ModalPermissionEditor.d.ts +++ b/imports/shared-app/ui/modal/permission/ModalDefinitions.d.ts @@ -1,14 +1,5 @@ -import { ConnectionHandler } from "tc-shared/ConnectionHandler"; -import * as React from "react"; -import { PermissionEditorTab } from "tc-shared/events/GlobalEvents"; +export declare type PermissionEditorTab = "groups-server" | "groups-channel" | "channel" | "client" | "client-channel"; export declare type PermissionEditorSubject = "groups-server" | "groups-channel" | "channel" | "client" | "client-channel" | "none"; -export declare const PermissionTabName: { - [T in PermissionEditorTab]: { - name: string; - useTranslate: () => string; - renderTranslate: () => React.ReactNode; - }; -}; export declare type GroupProperties = { id: number; type: "query" | "template" | "normal"; @@ -182,11 +173,6 @@ export interface PermissionModalEvents { property: "name" | "icon"; value: any; }; + notify_initial_rendered: {}; notify_destroy: {}; } -export declare type DefaultTabValues = { - groupId?: number; - channelId?: number; - clientDatabaseId?: number; -}; -export declare function spawnPermissionEditorModal(connection: ConnectionHandler, defaultTab?: PermissionEditorTab, values?: DefaultTabValues): void; diff --git a/imports/shared-app/ui/modal/permission/ModalRenderer.d.ts b/imports/shared-app/ui/modal/permission/ModalRenderer.d.ts new file mode 100644 index 0000000..1cb333b --- /dev/null +++ b/imports/shared-app/ui/modal/permission/ModalRenderer.d.ts @@ -0,0 +1,32 @@ +import * as React from "react"; +import { IpcRegistryDescription, Registry } from "tc-shared/events"; +import { Translatable } from "tc-shared/ui/react-elements/i18n"; +import { PermissionEditorEvents } from "tc-shared/ui/modal/permission/EditorDefinitions"; +import { PermissionEditorTab, PermissionModalEvents } from "tc-shared/ui/modal/permission/ModalDefinitions"; +import { AbstractModal } from "tc-shared/ui/react-elements/modal/Definitions"; +export declare type PermissionEditorServerInfo = { + handlerId: string; + serverUniqueId: string; +}; +export declare const PermissionTabName: { + [T in PermissionEditorTab]: { + name: string; + useTranslate: () => string; + renderTranslate: () => React.ReactNode; + }; +}; +export declare type DefaultTabValues = { + groupId?: number; + channelId?: number; + clientDatabaseId?: number; +}; +export declare class PermissionEditorModal extends AbstractModal { + readonly serverInfo: PermissionEditorServerInfo; + readonly modalEvents: Registry; + readonly editorEvents: Registry; + constructor(serverInfo: PermissionEditorServerInfo, modalEvents: IpcRegistryDescription, editorEvents: IpcRegistryDescription); + protected onDestroy(): void; + renderBody(): JSX.Element; + renderTitle(): React.ReactElement; +} +export default PermissionEditorModal; diff --git a/imports/shared-app/ui/modal/permission/TabHandler.d.ts b/imports/shared-app/ui/modal/permission/TabHandler.d.ts deleted file mode 100644 index 450782b..0000000 --- a/imports/shared-app/ui/modal/permission/TabHandler.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -import * as React from "react"; -import { Registry } from "tc-shared/events"; -import { PermissionModalEvents } from "tc-shared/ui/modal/permission/ModalPermissionEditor"; -import { PermissionEditorEvents } from "tc-shared/ui/modal/permission/PermissionEditor"; -import { ConnectionHandler } from "tc-shared/ConnectionHandler"; -export declare class SideBar extends React.Component<{ - connection: ConnectionHandler; - modalEvents: Registry; - editorEvents: Registry; -}, {}> { - render(): JSX.Element[]; -} diff --git a/imports/shared-app/ui/modal/settings/Microphone.d.ts b/imports/shared-app/ui/modal/settings/Microphone.d.ts index 77c2b80..05297ba 100644 --- a/imports/shared-app/ui/modal/settings/Microphone.d.ts +++ b/imports/shared-app/ui/modal/settings/Microphone.d.ts @@ -1,76 +1,3 @@ import { Registry } from "tc-shared/events"; -import { DeviceListState } from "tc-shared/audio/Recorder"; -export declare type MicrophoneSetting = "volume" | "vad-type" | "ppt-key" | "ppt-release-delay" | "ppt-release-delay-active" | "threshold-threshold" | "rnnoise"; -export declare type MicrophoneDevice = { - id: string; - name: string; - driver: string; - default: boolean; -}; -export declare type SelectedMicrophone = { - type: "default"; -} | { - type: "none"; -} | { - type: "device"; - deviceId: string; -}; -export declare type MicrophoneDevices = { - status: "error"; - error: string; -} | { - status: "audio-not-initialized"; -} | { - status: "no-permissions"; - shouldAsk: boolean; -} | { - status: "success"; - devices: MicrophoneDevice[]; - selectedDevice: SelectedMicrophone; -}; -export interface MicrophoneSettingsEvents { - "query_devices": { - refresh_list: boolean; - }; - "query_help": {}; - "query_setting": { - setting: MicrophoneSetting; - }; - "action_help_click": {}; - "action_request_permissions": {}; - "action_set_selected_device": { - target: SelectedMicrophone; - }; - "action_set_selected_device_result": { - status: "error"; - reason: string; - }; - "action_set_setting": { - setting: MicrophoneSetting; - value: any; - }; - notify_setting: { - setting: MicrophoneSetting; - value: any; - }; - notify_devices: MicrophoneDevices; - notify_device_selected: { - device: SelectedMicrophone; - }; - notify_device_level: { - level: { - [key: string]: { - deviceId: string; - status: "success" | "error"; - level?: number; - error?: string; - }; - }; - status: Exclude; - }; - notify_highlight: { - field: "hs-0" | "hs-1" | "hs-2" | undefined; - }; - notify_destroy: {}; -} +import { MicrophoneSettingsEvents } from "tc-shared/ui/modal/settings/MicrophoneDefinitions"; export declare function initialize_audio_microphone_controller(events: Registry): void; diff --git a/imports/shared-app/ui/modal/settings/MicrophoneDefinitions.d.ts b/imports/shared-app/ui/modal/settings/MicrophoneDefinitions.d.ts new file mode 100644 index 0000000..39a822a --- /dev/null +++ b/imports/shared-app/ui/modal/settings/MicrophoneDefinitions.d.ts @@ -0,0 +1,88 @@ +import { DeviceListState } from "tc-shared/audio/Recorder"; +export declare type MicrophoneSetting = "volume" | "vad-type" | "ppt-key" | "ppt-release-delay" | "ppt-release-delay-active" | "threshold-threshold" | "rnnoise"; +export declare type MicrophoneDevice = { + id: string; + name: string; + driver: string; + default: boolean; +}; +export declare type SelectedMicrophone = { + type: "default"; +} | { + type: "none"; +} | { + type: "device"; + deviceId: string; +}; +export declare type MicrophoneDevices = { + status: "error"; + error: string; +} | { + status: "audio-not-initialized"; +} | { + status: "no-permissions"; + shouldAsk: boolean; +} | { + status: "success"; + devices: MicrophoneDevice[]; + selectedDevice: SelectedMicrophone; +}; +export declare type InputDeviceLevel = { + status: "success"; + level: number; +} | { + status: "uninitialized"; +} | { + status: "error"; + message: string; +}; +export interface MicrophoneSettingsEvents { + "query_devices": { + refresh_list: boolean; + }; + "query_help": {}; + "query_setting": { + setting: MicrophoneSetting; + }; + "query_input_level": {}; + "action_help_click": {}; + "action_request_permissions": {}; + "action_set_selected_device": { + target: SelectedMicrophone; + }; + "action_set_selected_device_result": { + status: "error"; + reason: string; + }; + "action_open_processor_properties": {}; + "action_set_setting": { + setting: MicrophoneSetting; + value: any; + }; + notify_setting: { + setting: MicrophoneSetting; + value: any; + }; + notify_devices: MicrophoneDevices; + notify_device_selected: { + device: SelectedMicrophone; + }; + notify_device_level: { + level: { + [key: string]: { + deviceId: string; + status: "success" | "error"; + level?: number; + error?: string; + }; + }; + status: Exclude; + }; + notify_input_level: { + level: InputDeviceLevel; + }; + notify_highlight: { + field: "hs-0" | "hs-1" | "hs-2" | undefined; + }; + notify_destroy: {}; +} diff --git a/imports/shared-app/ui/modal/settings/MicrophoneRenderer.d.ts b/imports/shared-app/ui/modal/settings/MicrophoneRenderer.d.ts index 4fd3a5f..39d37af 100644 --- a/imports/shared-app/ui/modal/settings/MicrophoneRenderer.d.ts +++ b/imports/shared-app/ui/modal/settings/MicrophoneRenderer.d.ts @@ -1,6 +1,6 @@ /// import { Registry } from "tc-shared/events"; -import { MicrophoneSettingsEvents } from "tc-shared/ui/modal/settings/Microphone"; +import { MicrophoneSettingsEvents } from "tc-shared/ui/modal/settings/MicrophoneDefinitions"; export declare const MicrophoneSettings: (props: { events: Registry; }) => JSX.Element; diff --git a/imports/shared-app/ui/modal/transfer/FileBrowserRenderer.d.ts b/imports/shared-app/ui/modal/transfer/FileBrowserRenderer.d.ts index 0c473e8..2810040 100644 --- a/imports/shared-app/ui/modal/transfer/FileBrowserRenderer.d.ts +++ b/imports/shared-app/ui/modal/transfer/FileBrowserRenderer.d.ts @@ -32,6 +32,7 @@ export declare class NavigationBar extends ReactComponentBase, prevState: Readonly, snapshot?: any): void; private onPathClicked; diff --git a/imports/shared-app/video-viewer/Controller.d.ts b/imports/shared-app/ui/modal/video-viewer/Controller.d.ts similarity index 100% rename from imports/shared-app/video-viewer/Controller.d.ts rename to imports/shared-app/ui/modal/video-viewer/Controller.d.ts diff --git a/imports/shared-app/video-viewer/Definitions.d.ts b/imports/shared-app/ui/modal/video-viewer/Definitions.d.ts similarity index 100% rename from imports/shared-app/video-viewer/Definitions.d.ts rename to imports/shared-app/ui/modal/video-viewer/Definitions.d.ts diff --git a/imports/shared-app/video-viewer/Renderer.d.ts b/imports/shared-app/ui/modal/video-viewer/Renderer.d.ts similarity index 100% rename from imports/shared-app/video-viewer/Renderer.d.ts rename to imports/shared-app/ui/modal/video-viewer/Renderer.d.ts diff --git a/imports/shared-app/video-viewer/W2GPlugin.d.ts b/imports/shared-app/ui/modal/video-viewer/W2GPlugin.d.ts similarity index 95% rename from imports/shared-app/video-viewer/W2GPlugin.d.ts rename to imports/shared-app/ui/modal/video-viewer/W2GPlugin.d.ts index d1f199a..b321199 100644 --- a/imports/shared-app/video-viewer/W2GPlugin.d.ts +++ b/imports/shared-app/ui/modal/video-viewer/W2GPlugin.d.ts @@ -1,6 +1,6 @@ -import { PluginCmdHandler, PluginCommandInvoker } from "../connection/PluginCmdHandler"; -import { Registry } from "../events"; import { PlayerStatus } from "../video-viewer/Definitions"; +import { Registry } from "tc-events"; +import { PluginCmdHandler, PluginCommandInvoker } from "tc-shared/connection/PluginCmdHandler"; export interface W2GEvents { notify_watcher_add: { watcher: W2GWatcher; diff --git a/imports/shared-app/ui/react-elements/Arrow.d.ts b/imports/shared-app/ui/react-elements/Arrow.d.ts new file mode 100644 index 0000000..009b9fa --- /dev/null +++ b/imports/shared-app/ui/react-elements/Arrow.d.ts @@ -0,0 +1,6 @@ +/// +export declare const Arrow: (props: { + direction: NavigationReason; + className?: string; + onClick?: () => void; +}) => JSX.Element; diff --git a/imports/shared-app/ui/react-elements/Checkbox.d.ts b/imports/shared-app/ui/react-elements/Checkbox.d.ts index d480386..298d60b 100644 --- a/imports/shared-app/ui/react-elements/Checkbox.d.ts +++ b/imports/shared-app/ui/react-elements/Checkbox.d.ts @@ -6,6 +6,7 @@ export interface CheckboxProperties { onChange?: (value: boolean) => void; value?: boolean; initialValue?: boolean; + className?: string; children?: never; } export interface CheckboxState { diff --git a/imports/shared-app/ui/react-elements/CountryIcon.d.ts b/imports/shared-app/ui/react-elements/CountryCode.d.ts similarity index 52% rename from imports/shared-app/ui/react-elements/CountryIcon.d.ts rename to imports/shared-app/ui/react-elements/CountryCode.d.ts index 1c57f67..275b6a2 100644 --- a/imports/shared-app/ui/react-elements/CountryIcon.d.ts +++ b/imports/shared-app/ui/react-elements/CountryCode.d.ts @@ -1,5 +1,5 @@ /// -export declare const CountryIcon: (props: { - country: string; +export declare const CountryCode: (props: { + alphaCode: string; className?: string; }) => JSX.Element; diff --git a/imports/shared-app/ui/react-elements/Icon.d.ts b/imports/shared-app/ui/react-elements/Icon.d.ts index 0fad052..044445e 100644 --- a/imports/shared-app/ui/react-elements/Icon.d.ts +++ b/imports/shared-app/ui/react-elements/Icon.d.ts @@ -1,5 +1,5 @@ -/// -import { RemoteIcon } from "tc-shared/file/Icons"; +import * as React from "react"; +import { RemoteIcon, RemoteIconInfo } from "tc-shared/file/Icons"; export declare const IconRenderer: (props: { icon: string; title?: string; @@ -10,3 +10,8 @@ export declare const RemoteIconRenderer: (props: { className?: string; title?: string; }) => JSX.Element; +export declare const RemoteIconInfoRenderer: React.MemoExoticComponent<(props: { + icon: RemoteIconInfo; + className?: string; + title?: string; +}) => JSX.Element>; diff --git a/imports/shared-app/ui/react-elements/Tooltip.d.ts b/imports/shared-app/ui/react-elements/Tooltip.d.ts index aecc918..d6934f6 100644 --- a/imports/shared-app/ui/react-elements/Tooltip.d.ts +++ b/imports/shared-app/ui/react-elements/Tooltip.d.ts @@ -26,3 +26,4 @@ export declare const IconTooltip: (props: { className?: string; outerClassName?: string; }) => JSX.Element; +export declare const TooltipHook: React.MemoExoticComponent<() => JSX.Element>; diff --git a/imports/shared-app/ui/react-elements/modal/Definitions.d.ts b/imports/shared-app/ui/react-elements/modal/Definitions.d.ts index 73d1a23..84e20e7 100644 --- a/imports/shared-app/ui/react-elements/modal/Definitions.d.ts +++ b/imports/shared-app/ui/react-elements/modal/Definitions.d.ts @@ -1,5 +1,4 @@ import { IpcRegistryDescription, Registry } from "tc-shared/events"; -import { VideoViewerEvents } from "tc-shared/video-viewer/Definitions"; import { ChannelEditEvents } from "tc-shared/ui/modal/channel-edit/Definitions"; import { EchoTestEvents } from "tc-shared/ui/modal/echo-test/Definitions"; import { ModalGlobalSettingsEditorEvents } from "tc-shared/ui/modal/global-settings-editor/Definitions"; @@ -9,6 +8,14 @@ import { IpcVariableDescriptor } from "tc-shared/ui/utils/IpcVariable"; import { ModalBookmarkEvents, ModalBookmarkVariables } from "tc-shared/ui/modal/bookmarks/Definitions"; import { ModalBookmarksAddServerEvents, ModalBookmarksAddServerVariables } from "tc-shared/ui/modal/bookmarks-add-server/Definitions"; import { ModalPokeEvents, ModalPokeVariables } from "tc-shared/ui/modal/poke/Definitions"; +import { ModalClientGroupAssignmentEvents, ModalClientGroupAssignmentVariables } from "tc-shared/ui/modal/group-assignment/Definitions"; +import { VideoViewerEvents } from "tc-shared/ui/modal/video-viewer/Definitions"; +import { PermissionModalEvents } from "tc-shared/ui/modal/permission/ModalDefinitions"; +import { PermissionEditorEvents } from "tc-shared/ui/modal/permission/EditorDefinitions"; +import { PermissionEditorServerInfo } from "tc-shared/ui/modal/permission/ModalRenderer"; +import { ModalAvatarUploadEvents, ModalAvatarUploadVariables } from "tc-shared/ui/modal/avatar-upload/Definitions"; +import { ModalInputProcessorEvents, ModalInputProcessorVariables } from "tc-shared/ui/modal/input-processor/Definitios"; +import { ModalAboutVariables } from "tc-shared/ui/modal/about/Definitions"; export declare type ModalType = "error" | "warning" | "info" | "none"; export declare type ModalRenderType = "page" | "dialog"; export interface ModalOptions { @@ -71,6 +78,8 @@ export interface ModalInstanceController { getEvents(): Registry; show(): Promise; hide(): Promise; + minimize(): Promise; + maximize(): Promise; destroy(): any; } export interface ModalController { @@ -92,6 +101,7 @@ export declare abstract class AbstractModal { type(): ModalType; color(): "none" | "blue"; verticalAlignment(): "top" | "center" | "bottom"; + /** @deprecated */ protected onInitialize(): void; protected onDestroy(): void; protected onClose(): void; @@ -114,4 +124,9 @@ export interface ModalConstructorArguments { "modal-bookmarks": [IpcRegistryDescription, IpcVariableDescriptor]; "modal-bookmark-add-server": [IpcRegistryDescription, IpcVariableDescriptor]; "modal-poked": [IpcRegistryDescription, IpcVariableDescriptor]; + "modal-assign-server-groups": [IpcRegistryDescription, IpcVariableDescriptor]; + "modal-permission-edit": [PermissionEditorServerInfo, IpcRegistryDescription, IpcRegistryDescription]; + "modal-avatar-upload": [IpcRegistryDescription, IpcVariableDescriptor, string]; + "modal-input-processor": [IpcRegistryDescription, IpcVariableDescriptor]; + "modal-about": [IpcRegistryDescription, IpcVariableDescriptor]; } diff --git a/imports/shared-app/ui/react-elements/modal/Renderer.d.ts b/imports/shared-app/ui/react-elements/modal/Renderer.d.ts index f9147e3..0045435 100644 --- a/imports/shared-app/ui/react-elements/modal/Renderer.d.ts +++ b/imports/shared-app/ui/react-elements/modal/Renderer.d.ts @@ -33,7 +33,6 @@ export declare class PageModalRenderer extends React.PureComponent<{ modalInstance: AbstractModal; onBackdropClicked: () => void; children: React.ReactElement; -}, { shown: boolean; }> { constructor(props: any); diff --git a/imports/shared-app/ui/react-elements/modal/external/Controller.d.ts b/imports/shared-app/ui/react-elements/modal/external/Controller.d.ts index 7a65753..cb8179e 100644 --- a/imports/shared-app/ui/react-elements/modal/external/Controller.d.ts +++ b/imports/shared-app/ui/react-elements/modal/external/Controller.d.ts @@ -1,6 +1,5 @@ import { Registry } from "tc-events"; -import { ModalOptions } from "tc-shared/ui/react-elements/modal/Definitions"; -import { ModalInstanceController, ModalInstanceEvents, ModalState } from "tc-shared/ui/react-elements/modal/Definitions"; +import { ModalInstanceController, ModalInstanceEvents, ModalOptions, ModalState } from "tc-shared/ui/react-elements/modal/Definitions"; export declare class ExternalModalController implements ModalInstanceController { private readonly modalType; private readonly modalOptions; @@ -10,6 +9,7 @@ export declare class ExternalModalController implements ModalInstanceController private ipcRemotePeerId; private ipcChannel; private readonly modalEvents; + private modalInitialized; private modalInitializeCallback; private windowId; private windowListener; @@ -20,6 +20,8 @@ export declare class ExternalModalController implements ModalInstanceController getState(): ModalState; show(): Promise; hide(): Promise; + minimize(): Promise; + maximize(): Promise; private mutateWindow; private handleWindowDestroyed; private registerIpcMessageHandler; diff --git a/imports/shared-app/ui/react-elements/modal/internal/index.d.ts b/imports/shared-app/ui/react-elements/modal/internal/index.d.ts index 2238007..094eff3 100644 --- a/imports/shared-app/ui/react-elements/modal/internal/index.d.ts +++ b/imports/shared-app/ui/react-elements/modal/internal/index.d.ts @@ -1,25 +1,40 @@ -import { ModalInstanceController, ModalInstanceEvents, ModalOptions, ModalState } from "tc-shared/ui/react-elements/modal/Definitions"; +import { AbstractModal, ModalInstanceController, ModalInstanceEvents, ModalOptions, ModalState } from "tc-shared/ui/react-elements/modal/Definitions"; +import * as React from "react"; import { RegisteredModal } from "tc-shared/ui/react-elements/modal/Registry"; import { Registry } from "tc-events"; +declare class InternalRendererInstance extends React.PureComponent<{ + instance: InternalModalInstance; +}, { + shown: boolean; +}> { + constructor(props: any); + render(): JSX.Element; + componentWillUnmount(): void; +} export declare class InternalModalInstance implements ModalInstanceController { + readonly instanceUniqueId: string; readonly events: Registry; + readonly refRendererInstance: React.RefObject; private readonly modalKlass; private readonly constructorArguments; - private readonly rendererInstance; private readonly modalOptions; private state; - private modalInstance; - private htmlContainer; + modalInstance: AbstractModal; private modalInitializePromise; constructor(modalType: RegisteredModal, constructorArguments: any[], modalOptions: ModalOptions); private constructModal; private destructModal; + private destructModalInstance; getState(): ModalState; getEvents(): Registry; show(): Promise; hide(): Promise; + minimize(): Promise; + maximize(): Promise; destroy(): void; - private getCloseCallback; - private getPopoutCallback; - private getMinimizeCallback; + getCloseCallback(): () => void; + getPopoutCallback(): () => void; + getMinimizeCallback(): any; } +export declare const InternalModalHook: React.MemoExoticComponent<() => JSX.Element>; +export {}; diff --git a/imports/shared-app/ui/tree/Definitions.d.ts b/imports/shared-app/ui/tree/Definitions.d.ts index 25a56be..f78c646 100644 --- a/imports/shared-app/ui/tree/Definitions.d.ts +++ b/imports/shared-app/ui/tree/Definitions.d.ts @@ -209,9 +209,6 @@ export interface ChannelTreeUIEvents { treeEntryId: number; unread: boolean; }; - notify_visibility_changed: { - visible: boolean; - }; notify_destroy: {}; } export declare type ChannelTreeDragEntry = { diff --git a/imports/shared-app/ui/tree/EntryTags.d.ts b/imports/shared-app/ui/tree/EntryTags.d.ts index 9d05817..139eefb 100644 --- a/imports/shared-app/ui/tree/EntryTags.d.ts +++ b/imports/shared-app/ui/tree/EntryTags.d.ts @@ -1,9 +1,11 @@ import * as React from "react"; +declare type EntryTagStyle = "text-only" | "normal"; export declare const ServerTag: React.MemoExoticComponent<(props: { serverName: string; handlerId: string; serverUniqueId?: string; className?: string; + style?: EntryTagStyle; }) => JSX.Element>; export declare const ClientTag: React.MemoExoticComponent<(props: { clientName: string; @@ -12,6 +14,7 @@ export declare const ClientTag: React.MemoExoticComponent<(props: { clientId?: number; clientDatabaseId?: number; className?: string; + style?: EntryTagStyle; }) => JSX.Element>; export declare const ChannelTag: React.MemoExoticComponent<(props: { channelName: string; @@ -19,3 +22,4 @@ export declare const ChannelTag: React.MemoExoticComponent<(props: { handlerId: string; className?: string; }) => JSX.Element>; +export {}; diff --git a/imports/shared-app/ui/tree/RendererView.d.ts b/imports/shared-app/ui/tree/RendererView.d.ts index bf78f47..20c911a 100644 --- a/imports/shared-app/ui/tree/RendererView.d.ts +++ b/imports/shared-app/ui/tree/RendererView.d.ts @@ -32,7 +32,6 @@ export declare class ChannelTreeView extends ReactComponentBase(variable: T, provider: (customData: any) => Variables[T] | Promise): void; + setVariableProviderAsync(variable: T, provider: (customData: any) => Promise): void; /** * @param variable * @param editor If the editor returns `false` or a new variable, such variable will be used diff --git a/imports/shared-app/update/Updater.d.ts b/imports/shared-app/update/Updater.d.ts index 6f01372..2855380 100644 --- a/imports/shared-app/update/Updater.d.ts +++ b/imports/shared-app/update/Updater.d.ts @@ -2,7 +2,10 @@ import { ChangeLog } from "../update/ChangeLog"; export interface Updater { getChangeLog(): ChangeLog; getChangeList(oldVersion: string): ChangeLog; - getLastUsedVersion(): string; + /** + * @returns `undefined` if `updateUsedVersion()` never has been called. + */ + getLastUsedVersion(): string | undefined; getCurrentVersion(): string; updateUsedVersion(): any; } diff --git a/imports/shared-app/voice/RecorderBase.d.ts b/imports/shared-app/voice/RecorderBase.d.ts index 12a3ca8..0fa5261 100644 --- a/imports/shared-app/voice/RecorderBase.d.ts +++ b/imports/shared-app/voice/RecorderBase.d.ts @@ -64,6 +64,112 @@ export declare enum FilterMode { */ Block = 2 } +/** + * All available options for input processing. + * Since input processing is only available on the native client these are the options + * the native client (especially WebRTC audio processing) have. + */ +export interface InputProcessorConfigWebRTC { + "pipeline.maximum_internal_processing_rate": number; + "pipeline.multi_channel_render": boolean; + "pipeline.multi_channel_capture": boolean; + "pre_amplifier.enabled": boolean; + "pre_amplifier.fixed_gain_factor": number; + "high_pass_filter.enabled": boolean; + "high_pass_filter.apply_in_full_band": boolean; + "echo_canceller.enabled": boolean; + "echo_canceller.mobile_mode": boolean; + "echo_canceller.export_linear_aec_output": boolean; + "echo_canceller.enforce_high_pass_filtering": boolean; + "noise_suppression.enabled": boolean; + "noise_suppression.level": "low" | "moderate" | "high" | "very-high"; + "noise_suppression.analyze_linear_aec_output_when_available": boolean; + "transient_suppression.enabled": boolean; + "voice_detection.enabled": boolean; + "gain_controller1.enabled": boolean; + "gain_controller1.mode": "adaptive-analog" | "adaptive-digital" | "fixed-digital"; + "gain_controller1.target_level_dbfs": number; + "gain_controller1.compression_gain_db": number; + "gain_controller1.enable_limiter": boolean; + "gain_controller1.analog_level_minimum": number; + "gain_controller1.analog_level_maximum": number; + "gain_controller1.analog_gain_controller.enabled": boolean; + "gain_controller1.analog_gain_controller.startup_min_volume": number; + "gain_controller1.analog_gain_controller.clipped_level_min": number; + "gain_controller1.analog_gain_controller.enable_agc2_level_estimator": boolean; + "gain_controller1.analog_gain_controller.enable_digital_adaptive": boolean; + "gain_controller2.enabled": boolean; + "gain_controller2.fixed_digital.gain_db": number; + "gain_controller2.adaptive_digital.enabled": boolean; + "gain_controller2.adaptive_digital.vad_probability_attack": number; + "gain_controller2.adaptive_digital.level_estimator": "rms" | "peak"; + "gain_controller2.adaptive_digital.level_estimator_adjacent_speech_frames_threshold": number; + "gain_controller2.adaptive_digital.use_saturation_protector": boolean; + "gain_controller2.adaptive_digital.initial_saturation_margin_db": number; + "gain_controller2.adaptive_digital.extra_saturation_margin_db": number; + "gain_controller2.adaptive_digital.gain_applier_adjacent_speech_frames_threshold": number; + "gain_controller2.adaptive_digital.max_gain_change_db_per_second": number; + "gain_controller2.adaptive_digital.max_output_noise_level_dbfs": number; + "residual_echo_detector.enabled": boolean; + "level_estimation.enabled": boolean; +} +/** + * Attention: + * These keys **MUST** be equal to all keys of `InputProcessorConfigWebRTC`. + * All keys not registered in here will not be consideration. + */ +export declare const kInputProcessorConfigWebRTCKeys: (keyof InputProcessorConfigWebRTC)[]; +export interface InputProcessorConfigRNNoise { + "rnnoise.enabled": boolean; +} +/** + * Attention: + * These keys **MUST** be equal to all keys of `InputProcessorConfigWebRTC`. + * All keys not registered in here will not be consideration. + */ +export declare const kInputProcessorConfigRNNoiseKeys: (keyof InputProcessorConfigRNNoise)[]; +export interface InputProcessorConfigMapping { + "webrtc-processing": InputProcessorConfigWebRTC; + "rnnoise": InputProcessorConfigRNNoise; +} +export declare type InputProcessorType = keyof InputProcessorConfigMapping; +export interface InputProcessorStatistics { + output_rms_dbfs: number | undefined; + voice_detected: number | undefined; + echo_return_loss: number | undefined; + echo_return_loss_enhancement: number | undefined; + divergent_filter_fraction: number | undefined; + delay_median_ms: number | undefined; + delay_standard_deviation_ms: number | undefined; + residual_echo_likelihood: number | undefined; + residual_echo_likelihood_recent_max: number | undefined; + delay_ms: number | undefined; + rnnoise_volume: number | undefined; +} +export interface InputProcessor { + /** + * @param processor Target processor type + * @returns `true` if the target processor type is supported and available + */ + hasProcessor(processor: InputProcessorType): boolean; + /** + * Get the processor config of the target type. + * This method will throw when the target processor isn't supported. + * @param processor Target processor type. + * @returns The processor config. + */ + getProcessorConfig(processor: T): InputProcessorConfigMapping[T]; + /** + * Apply the target config. + * @param processor + * @param config + */ + applyProcessorConfig(processor: T, config: InputProcessorConfigMapping[T]): any; + /** + * Get the current processor statistics. + */ + getStatistics(): InputProcessorStatistics; +} export interface AbstractInput { readonly events: Registry; currentState(): InputState; @@ -89,6 +195,13 @@ export interface AbstractInput { removeFilter(filter: Filter): any; getVolume(): number; setVolume(volume: number): any; + getInputProcessor(): InputProcessor; + /** + * Create a new level meter for this audio input. + * This level meter will be indicate the audio level after all processing. + * Note: Changing the input device or stopping the input will result in no activity. + */ + createLevelMeter(): LevelMeter; } export interface LevelMeter { getDevice(): InputDevice; diff --git a/imports/shared-app/voice/RecorderProfile.d.ts b/imports/shared-app/voice/RecorderProfile.d.ts index 60b726f..874ba0c 100644 --- a/imports/shared-app/voice/RecorderProfile.d.ts +++ b/imports/shared-app/voice/RecorderProfile.d.ts @@ -29,6 +29,27 @@ export declare const defaultRecorderEvents: Registry; export declare function setDefaultRecorder(recorder: RecorderProfile): void; export interface RecorderProfileEvents { notify_device_changed: {}; + notify_voice_start: {}; + notify_voice_end: {}; + notify_input_initialized: {}; +} +export declare abstract class RecorderProfileOwner { + /** + * This method will be called from the recorder profile. + */ + protected abstract handleUnmount(): any; + /** + * This callback will be called when the recorder audio input has + * been initialized. + * Note: This method might be called within ownRecorder(). + * If this method has been called, handleUnmount will be called. + * + * @param input The target input. + */ + protected abstract handleRecorderInput(input: AbstractInput): any; +} +export declare abstract class ConnectionRecorderProfileOwner extends RecorderProfileOwner { + abstract getConnection(): ConnectionHandler; } export declare class RecorderProfile { readonly events: Registry; @@ -36,11 +57,9 @@ export declare class RecorderProfile { readonly volatile: any; config: RecorderProfileConfig; input: AbstractInput; + private currentOwner; + private currentOwnerMutex; current_handler: ConnectionHandler; - callback_input_initialized: (input: AbstractInput) => void; - callback_start: () => any; - callback_stop: () => any; - callback_unmount: () => any; private readonly pptHook; private pptTimeout; private pptHookRegistered; @@ -52,6 +71,13 @@ export declare class RecorderProfile { private save; private reinitializePPTHook; private reinitializeFilter; + /** + * Own the recorder. + */ + ownRecorder(target: RecorderProfileOwner | undefined): Promise; + getOwner(): RecorderProfileOwner | undefined; + isInputActive(): boolean; + /** @deprecated use `ownRecorder(undefined)` */ unmount(): Promise; getVadType(): VadType; setVadType(type: VadType): boolean; diff --git a/jenkins/create_build.sh b/jenkins/create_build.sh index 8fd81cd..8de6d7c 100755 --- a/jenkins/create_build.sh +++ b/jenkins/create_build.sh @@ -123,5 +123,5 @@ function deploy_client() { #install_npm #compile_scripts #compile_native -package_client +#package_client deploy_client diff --git a/modules/renderer-manifest/index.ts b/modules/renderer-manifest/index.ts index baa2a99..05431ec 100644 --- a/modules/renderer-manifest/index.ts +++ b/modules/renderer-manifest/index.ts @@ -39,24 +39,11 @@ export function initialize() { window.displayCriticalError = _impl; } - loader.register_task(loader.Stage.JAVASCRIPT, { - name: "teaclient jquery", + loader.register_task(Stage.JAVASCRIPT, { + name: "handler initialize #2", + priority: -1, function: async () => { - window.$ = require("jquery"); - - window.jQuery = window.$; - Object.assign(window.$, window.jsrender = require('jsrender')); - }, - priority: 80 - }); - - loader.register_task(Stage.JAVASCRIPT_INITIALIZING, { - name: "handler initialize", - priority: 80, - function: async () => { - await import("../renderer/Logger"); - await import("../renderer/PersistentLocalStorage"); - await import("../renderer/ContextMenu"); + await import("../renderer/hooks/StorageAdapter"); } - }) + }); } \ No newline at end of file diff --git a/modules/renderer/ClientStorage.ts b/modules/renderer/ClientStorage.ts new file mode 100644 index 0000000..10e62ba --- /dev/null +++ b/modules/renderer/ClientStorage.ts @@ -0,0 +1,65 @@ +import * as electron from "electron"; +import * as path from "path"; +import * as fs from "fs-extra"; +import {StorageAdapter} from "tc-shared/StorageAdapter"; +import {ServerSettingsStorage} from "tc-shared/ServerSettings"; +import {LogCategory, logError} from "tc-shared/log"; + +const kStoragePath = path.join(electron.remote.app.getPath("userData"), "settings"); + +function storageKeyPath(key: string) { + return path.join(kStoragePath, encodeURIComponent(key)); +} + +export class ClientStorageAdapter implements StorageAdapter { + delete(key: string): Promise { + return fs.remove(storageKeyPath(key)).catch(error => {}); + } + + async get(key: string): Promise { + try { + const result = await fs.readFile(storageKeyPath(key)); + return JSON.parse(result.toString()); + } catch (error) { + logError(LogCategory.GENERAL, tr("Failed to load client storage key %s: %o"), key, error); + return null; + } + } + + has(key: string): Promise { + return fs.pathExists(storageKeyPath(key)); + } + + set(key: string, value: string): Promise { + return fs.writeFile(storageKeyPath(key), JSON.stringify(value)).catch(error => { + logError(LogCategory.GENERAL, tr("Failed to set client storage key %s: %o"), key, error); + }); + } +} +export let clientStorage; + +export class ClientServerSettingsStorage implements ServerSettingsStorage { + get(serverUniqueId: string): string { + try { + const result = fs.readFileSync(storageKeyPath("settings.server_" + serverUniqueId)); + return JSON.parse(result.toString()); + } catch (error) { + logError(LogCategory.GENERAL, tr("Failed to load individual server settings for %s: %o"), serverUniqueId, error); + return null; + } + } + + set(serverUniqueId: string, value: string) { + try { + fs.writeFileSync(storageKeyPath("settings.server_" + serverUniqueId), JSON.stringify(value)); + } catch (error) { + logError(LogCategory.GENERAL, tr("Failed to write individual server settings for %s: %o"), serverUniqueId, error); + return null; + } + } +} + +export async function initializeClientStorage() { + await fs.mkdirp(kStoragePath); + clientStorage = new ClientStorageAdapter(); +} \ No newline at end of file diff --git a/modules/renderer/ClientUpdater.ts b/modules/renderer/ClientUpdater.ts index a5bbb01..dfc3879 100644 --- a/modules/renderer/ClientUpdater.ts +++ b/modules/renderer/ClientUpdater.ts @@ -3,13 +3,15 @@ import * as path from "path"; import * as fs from "fs-extra"; import {Updater} from "tc-shared/update/Updater"; import {ChangeLog, ChangeSetEntry} from "tc-shared/update/ChangeLog"; +import {settings, Settings} from "tc-shared/settings"; function getChangeLogFile() { const app_path = electron.remote.app.getAppPath(); - if(app_path.endsWith(".asar")) + if(app_path.endsWith(".asar")) { return path.join(path.dirname(app_path), "..", "ChangeLog.txt"); - else - return path.join(app_path, "github", "ChangeLog.txt"); /* We've the source master :D */ + } else { + return path.join(app_path, "github", "ChangeLog.txt"); /* We've the source :D */ + } } const EntryRegex = /^([0-9]+)\.([0-9]+)\.([0-9]+)(-b[0-9]+)?:$/m; @@ -31,8 +33,9 @@ function parseChangeLogEntry(lines: string[], index: number) : { entries: Change if(trimmed[0] === '-') { const depth = lines[index].indexOf('-'); if(depth > entryDepth) { - if(typeof currentEntry === "undefined") + if(typeof currentEntry === "undefined") { throw "missing change child entries parent at line " + index; + } const result = parseChangeLogEntry(lines, index); entries.push({ @@ -45,8 +48,9 @@ function parseChangeLogEntry(lines: string[], index: number) : { entries: Change break; } else { /* new entry */ - if(typeof currentEntry === "string") + if(typeof currentEntry === "string") { entries.push(currentEntry); + } currentEntry = trimmed.substr(1).trim(); } @@ -57,8 +61,9 @@ function parseChangeLogEntry(lines: string[], index: number) : { entries: Change index++; } - if(typeof currentEntry === "string") + if(typeof currentEntry === "string") { entries.push(currentEntry); + } return { index: index, @@ -75,8 +80,9 @@ async function parseClientChangeLog() : Promise { const lines = (await fs.readFile(getChangeLogFile())).toString("UTF-8").split("\n"); let index = 0; - while(index < lines.length && !lines[index].match(EntryRegex)) + while(index < lines.length && !lines[index].match(EntryRegex)) { index++; + } while(index < lines.length) { const [ _, major, minor, patch, build ] = lines[index].match(EntryRegex); @@ -93,7 +99,6 @@ async function parseClientChangeLog() : Promise { return result; } -const kLastUsedVersionKey = "updater-used-version-native"; export class ClientUpdater implements Updater { private changeLog: ChangeLog; private currentVersion: string; @@ -131,10 +136,10 @@ export class ClientUpdater implements Updater { } getLastUsedVersion(): string { - return localStorage.getItem(kLastUsedVersionKey) || "1.4.9"; + return settings.getValue(Settings.KEY_UPDATER_LAST_USED_CLIENT, undefined); } updateUsedVersion() { - localStorage.setItem(kLastUsedVersionKey, this.getCurrentVersion()); + settings.setValue(Settings.KEY_UPDATER_LAST_USED_CLIENT, this.getCurrentVersion()); } } \ No newline at end of file diff --git a/modules/renderer/ContextMenu.ts b/modules/renderer/ContextMenu.ts index 6d2c4ab..689af92 100644 --- a/modules/renderer/ContextMenu.ts +++ b/modules/renderer/ContextMenu.ts @@ -1,4 +1,4 @@ -import {ContextMenuEntry, ContextMenuFactory, setGlobalContextMenuFactory} from "tc-shared/ui/ContextMenu"; +import {ContextMenuEntry, ContextMenuFactory} from "tc-shared/ui/ContextMenu"; import * as electron from "electron"; import {MenuItemConstructorOptions} from "electron"; import {clientIconClassToImage, remoteIconDatafier, RemoteIconWrapper} from "./IconHelper"; @@ -109,7 +109,7 @@ class ContextMenuInstance { } } -setGlobalContextMenuFactory(new class implements ContextMenuFactory { +export class ClientContextMenuFactory implements ContextMenuFactory { closeContextMenu() { currentMenu?.destroy(); currentMenu = undefined; @@ -120,4 +120,4 @@ setGlobalContextMenuFactory(new class implements ContextMenuFactory { currentMenu = new ContextMenuInstance(entries, callbackClose); currentMenu.spawn(position.pageX, position.pageY); } -}); \ No newline at end of file +}; \ No newline at end of file diff --git a/modules/renderer/Logger.ts b/modules/renderer/Logger.ts index eb66505..85ff17c 100644 --- a/modules/renderer/Logger.ts +++ b/modules/renderer/Logger.ts @@ -37,7 +37,7 @@ function create_logger(name: string) : Logger { const log = (type, message: string, ...args) => { switch (type) { case LogType.TRACE: - original_console.trace(message, ...args); + original_console.debug(message, ...args); break; case LogType.DEBUG: original_console.debug(message, ...args); diff --git a/modules/renderer/PersistentLocalStorage.ts b/modules/renderer/PersistentLocalStorage.ts deleted file mode 100644 index abd9a06..0000000 --- a/modules/renderer/PersistentLocalStorage.ts +++ /dev/null @@ -1,99 +0,0 @@ -import * as electron from "electron"; -import * as path from "path"; -import * as fs from "fs-extra"; - -const APP_DATA = electron.remote.app.getPath("userData"); -const SETTINGS_DIR = path.join(APP_DATA, "settings"); - -let _local_storage: {[key: string]: any} = {}; -let _local_storage_save: {[key: string]: boolean} = {}; -let _save_timer: number; - -export async function initialize() { - await fs.mkdirp(SETTINGS_DIR); - - console.error("Load local storage from: %o", SETTINGS_DIR); - const files = await fs.readdir(SETTINGS_DIR); - for(const file of files) { - const key = decodeURIComponent(file); - console.log("Load settings: %s", key); - - try { - const data = await fs.readFile(path.join(SETTINGS_DIR, file)); - _local_storage[key] = JSON.parse(data.toString() || "{}"); - } catch(error) { - const target_file = path.join(SETTINGS_DIR, file + "." + Date.now() + ".broken"); - console.error("Failed to load settings for %s: %o. Moving settings so the file does not get overridden. Target file: %s", key, error, target_file); - try { - await fs.move(path.join(SETTINGS_DIR, file), target_file); - } catch (error) { - console.warn("Failed to move broken settings file!"); - } - } - } - - let _new_storage: Storage = {} as any; - - _new_storage.getItem = key => _local_storage[key] || null; - _new_storage.setItem = (key, value) => { - _local_storage[key] = value; - _local_storage_save[key] = true; - (_new_storage as any)["length"] = Object.keys(_local_storage).length; - }; - - _new_storage.clear = () => { - _local_storage = {}; - _local_storage_save = {}; - - try { - fs.emptyDirSync(SETTINGS_DIR); - } catch(error) { - console.warn("Failed to empty settings dir"); - } - (_new_storage as any)["length"] = 0; - }; - - _new_storage.key = index => Object.keys(_local_storage)[index]; - _new_storage.removeItem = key => { - delete _local_storage[key]; - delete_key(key).catch(error => { - console.warn("Failed to delete key on fs: %s => %o", key, error); - }); - (_new_storage as any)["length"] = Object.keys(_local_storage).length; - }; - Object.assign(window.localStorage, _new_storage); - - /* try to save everything all 60 seconds */ - _save_timer = setInterval(() => { - save_all_sync(); - }, 60 * 1000); -} - -export function save_all_sync() { - for(const key of Object.keys(_local_storage_save)) - save_key_sync(key); -} - -function key_path(key: string) { - return path.join(SETTINGS_DIR, encodeURIComponent(key)); -} - -export function save_key_sync(key: string) { - if(!_local_storage_save[key]) - return; - - delete _local_storage_save[key]; - const setting_path = key_path(key); - fs.writeJsonSync(setting_path, _local_storage[key], {spaces: 0}); -} - -export async function delete_key(key: string) { - delete _local_storage_save[key]; - const setting_path = key_path(key); - await fs.remove(setting_path); /* could be async because we're not carrying about data */ -} - -window.addEventListener("beforeunload", () => { - console.log("Save local storage"); - save_all_sync(); -}); \ No newline at end of file diff --git a/modules/renderer/WindowManager.ts b/modules/renderer/WindowManager.ts index e4609d4..acf6e8e 100644 --- a/modules/renderer/WindowManager.ts +++ b/modules/renderer/WindowManager.ts @@ -25,6 +25,10 @@ export class NativeWindowManager implements WindowManager { constructor() { this.windowInstances = {}; this.events = new Registry(); + + window.onunload = () => { + Object.values(this.windowInstances).forEach(window => window.destroy()); + }; } getEvents(): Registry { diff --git a/modules/renderer/audio/AudioRecorder.ts b/modules/renderer/audio/AudioRecorder.ts index dbe9be8..c58d3c5 100644 --- a/modules/renderer/audio/AudioRecorder.ts +++ b/modules/renderer/audio/AudioRecorder.ts @@ -4,8 +4,14 @@ import { InputConsumer, InputConsumerType, InputEvents, + InputProcessor, + InputProcessorConfigMapping, + InputProcessorStatistics, + InputProcessorType, InputStartError, InputState, + kInputProcessorConfigRNNoiseKeys, + kInputProcessorConfigWebRTCKeys, LevelMeter, } from "tc-shared/voice/RecorderBase"; import {audio} from "tc-native/connection"; @@ -17,15 +23,16 @@ import {getRecorderBackend, InputDevice} from "tc-shared/audio/Recorder"; import {LogCategory, logError, logTrace, logWarn} from "tc-shared/log"; import {Settings, settings} from "tc-shared/settings"; import NativeFilterMode = audio.record.FilterMode; +import AudioProcessor = audio.record.AudioProcessor; export class NativeInput implements AbstractInput { - static readonly instances = [] as NativeInput[]; - readonly events: Registry; - readonly nativeHandle: audio.record.AudioRecorder; - readonly nativeConsumer: audio.record.AudioConsumer; + private readonly inputProcessor: NativeInputProcessor; + private readonly nativeHandle: audio.record.AudioRecorder; + private readonly nativeConsumer: audio.record.AudioConsumer; + private listenerRNNoise: () => void; private state: InputState; private deviceId: string | undefined; @@ -36,9 +43,11 @@ export class NativeInput implements AbstractInput { this.events = new Registry(); this.nativeHandle = audio.record.create_recorder(); + this.inputProcessor = new NativeInputProcessor(this.nativeHandle.get_audio_processor()); + this.inputProcessor.applyProcessorConfig("rnnoise", { "rnnoise.enabled": settings.getValue(Settings.KEY_RNNOISE_FILTER) }); + this.listenerRNNoise = settings.globalChangeListener(Settings.KEY_RNNOISE_FILTER, newValue => this.inputProcessor.applyProcessorConfig("rnnoise", { "rnnoise.enabled": newValue })); this.nativeConsumer = this.nativeHandle.create_consumer(); - this.nativeConsumer.toggle_rnnoise(settings.getValue(Settings.KEY_RNNOISE_FILTER)); this.nativeConsumer.callback_ended = () => { this.filtered = true; @@ -50,13 +59,12 @@ export class NativeInput implements AbstractInput { }; this.state = InputState.PAUSED; - NativeInput.instances.push(this); } destroy() { - const index = NativeInput.instances.indexOf(this); - if(index !== -1) { - NativeInput.instances.splice(index, 1); + if(this.listenerRNNoise) { + this.listenerRNNoise(); + this.listenerRNNoise = undefined; } } @@ -267,43 +275,145 @@ export class NativeInput implements AbstractInput { this.nativeConsumer.set_filter_mode(nativeMode); this.events.fire("notify_filter_mode_changed", { oldMode, newMode: mode }); } + + getInputProcessor(): InputProcessor { + return this.inputProcessor; + } + + createLevelMeter(): LevelMeter { + return new NativeInputLevelMeter(this.nativeHandle.create_level_meter("post-process")); + } +} + +class NativeInputProcessor implements InputProcessor { + private readonly processor: AudioProcessor; + + constructor(processor: AudioProcessor) { + this.processor = processor; + } + + applyProcessorConfig(processor: T, config: Partial) { + let keys: string[]; + switch (processor) { + case "webrtc-processing": + keys = kInputProcessorConfigWebRTCKeys; + break; + + case "rnnoise": + keys = kInputProcessorConfigRNNoiseKeys; + break; + + default: + throw "invalid processor"; + } + + const filteredConfig = {}; + keys.forEach(key => { + if(typeof config[key] === "undefined") { + return; + } + + filteredConfig[key] = config[key]; + }); + this.processor.apply_config(filteredConfig); + } + + getProcessorConfig(processor: T): InputProcessorConfigMapping[T] { + let keys: string[]; + const config = this.processor.get_config(); + + switch (processor) { + case "webrtc-processing": + keys = kInputProcessorConfigWebRTCKeys; + break; + + case "rnnoise": + keys = kInputProcessorConfigRNNoiseKeys; + break; + + default: + throw "invalid processor"; + } + + const result = {}; + keys.forEach(key => result[key] = config[key]); + return result as any; + } + + getStatistics(): InputProcessorStatistics { + return this.processor.get_statistics(); + } + + hasProcessor(processor: InputProcessorType): boolean { + switch (processor) { + case "rnnoise": + case "webrtc-processing": + return true; + + default: + return false; + } + } +} + +class NativeInputLevelMeter implements LevelMeter { + private nativeHandle: audio.record.AudioLevelMeter; + + constructor(nativeHandle: audio.record.AudioLevelMeter) { + this.nativeHandle = nativeHandle; + this.nativeHandle.start(error => { + if(typeof error !== "undefined") { + logError(LogCategory.AUDIO, tr("Native input audio level meter failed to start. This should not happen. Reason: %o"), error); + } + }); + } + + destroy(): any { + this.nativeHandle?.set_callback(undefined); + this.nativeHandle?.stop(); + this.nativeHandle = undefined; + } + + getDevice(): InputDevice { + return undefined; + } + + setObserver(callback: (value: number) => any) { + this.nativeHandle.set_callback(level => { + try { + callback(level); + } catch (error) { + console.error(error); + } + }); + } } export class NativeLevelMeter implements LevelMeter { - static readonly instances: NativeLevelMeter[] = []; readonly targetDevice: InputDevice; - public nativeRecorder: audio.record.AudioRecorder; - public nativeConsumer: audio.record.AudioConsumer; - - private callback: (num: number) => any; - private nativeFilter: audio.record.ThresholdConsumeFilter; + private nativeHandle: audio.record.AudioLevelMeter; constructor(device: InputDevice) { this.targetDevice = device; - this.callback = () => {}; } async initialize() { try { - this.nativeRecorder = audio.record.create_recorder(); - this.nativeConsumer = this.nativeRecorder.create_consumer(); - - this.nativeConsumer.toggle_rnnoise(settings.getValue(Settings.KEY_RNNOISE_FILTER)); - - this.nativeFilter = this.nativeConsumer.create_filter_threshold(.5); - this.nativeFilter.set_attack_smooth(.75); - this.nativeFilter.set_release_smooth(.75); - - await new Promise(resolve => this.nativeRecorder.set_device(this.targetDevice.deviceId, resolve)); + this.nativeHandle = audio.record.create_device_level_meter(this.targetDevice.deviceId); await new Promise((resolve, reject) => { - this.nativeRecorder.start(flag => { - if (typeof flag === "boolean" && flag) - resolve(); - else - reject(typeof flag === "string" ? flag : "failed to start"); + this.nativeHandle.start(error => { + if(typeof error !== "undefined") { + reject(error); + } else { + resolve(error); + } }); }); + + /* TODO: May implement smoothing to the native level meter as well? */ + //this.nativeFilter.set_attack_smooth(.75); + //this.nativeFilter.set_release_smooth(.75); } catch (error) { if (typeof (error) === "string") { throw error; @@ -312,40 +422,25 @@ export class NativeLevelMeter implements LevelMeter { logWarn(LogCategory.AUDIO, tr("Failed to initialize level meter for device %o: %o"), this.targetDevice, error); throw "initialize failed (lookup console)"; } - - /* references this variable, needs a destroy() call, else memory leak */ - this.nativeFilter.set_analyze_filter(value => this.callback(value)); - NativeLevelMeter.instances.push(this); } destroy() { - const index = NativeLevelMeter.instances.indexOf(this); - if(index !== -1) { - NativeLevelMeter.instances.splice(index, 1); - } - - if (this.nativeFilter) { - this.nativeFilter.set_analyze_filter(undefined); - this.nativeConsumer.unregister_filter(this.nativeFilter); - } - - if (this.nativeConsumer) { - this.nativeRecorder.delete_consumer(this.nativeConsumer); - } - - if(this.nativeRecorder) { - this.nativeRecorder.stop(); - } - this.nativeRecorder = undefined; - this.nativeConsumer = undefined; - this.nativeFilter = undefined; + this.nativeHandle?.set_callback(undefined); + this.nativeHandle?.stop(); + this.nativeHandle = undefined; } getDevice(): InputDevice { return this.targetDevice; } - setObserver(callback: (value: number) => any) { - this.callback = callback || (() => {}); + setObserver(callback: (value: number) => void) { + this.nativeHandle.set_callback(level => { + try { + callback(level); + } catch (error) { + console.error(error); + } + }); } } \ No newline at end of file diff --git a/modules/renderer/connection/ServerConnection.ts b/modules/renderer/connection/ServerConnection.ts index a081fe8..6ebddb4 100644 --- a/modules/renderer/connection/ServerConnection.ts +++ b/modules/renderer/connection/ServerConnection.ts @@ -171,7 +171,7 @@ class ErrorCommandHandler extends AbstractCommandHandler { connection_bandwidth_received_last_minute_speech: 0, connection_bandwidth_received_last_minute_keepalive: 0, connection_bandwidth_received_last_minute_control: 0 - } + }, { process_result: false } ); } return false; @@ -211,9 +211,23 @@ export class ServerConnection extends AbstractServerConnection { this.nativeHandle = spawn_native_server_connection(); this.nativeHandle.callback_disconnect = reason => { - this.client.handleDisconnect(DisconnectReason.CONNECTION_CLOSED, { - reason: reason - }); + switch (this.connectionState) { + case ConnectionState.CONNECTING: + case ConnectionState.AUTHENTICATING: + case ConnectionState.INITIALISING: + this.client.handleDisconnect(DisconnectReason.CONNECT_FAILURE, reason); + break; + + case ConnectionState.CONNECTED: + this.client.handleDisconnect(DisconnectReason.CONNECTION_CLOSED, { + reason: reason + }); + break; + + case ConnectionState.DISCONNECTING: + case ConnectionState.UNCONNECTED: + break; + } }; this.nativeHandle.callback_command = (command, args, switches) => { console.log("Received: %o %o %o", command, args, switches); diff --git a/modules/renderer/connection/VoiceConnection.ts b/modules/renderer/connection/VoiceConnection.ts index 1343e3b..8682e58 100644 --- a/modules/renderer/connection/VoiceConnection.ts +++ b/modules/renderer/connection/VoiceConnection.ts @@ -3,18 +3,20 @@ import { VoiceConnectionStatus, WhisperSessionInitializer } from "tc-shared/connection/VoiceConnection"; -import {RecorderProfile} from "tc-shared/voice/RecorderProfile"; +import {ConnectionRecorderProfileOwner, RecorderProfile} from "tc-shared/voice/RecorderProfile"; import {NativeServerConnection, NativeVoiceClient, NativeVoiceConnection, PlayerState} from "tc-native/connection"; import {ServerConnection} from "./ServerConnection"; import {VoiceClient} from "tc-shared/voice/VoiceClient"; import {WhisperSession, WhisperTarget} from "tc-shared/voice/VoiceWhisper"; import {NativeInput} from "../audio/AudioRecorder"; -import {ConnectionState} from "tc-shared/ConnectionHandler"; +import {ConnectionHandler, ConnectionState} from "tc-shared/ConnectionHandler"; import {VoicePlayerEvents, VoicePlayerLatencySettings, VoicePlayerState} from "tc-shared/voice/VoicePlayer"; import {Registry} from "tc-shared/events"; -import {LogCategory, logDebug, logInfo, logWarn} from "tc-shared/log"; +import {LogCategory, logError, logInfo, logWarn} from "tc-shared/log"; import {tr} from "tc-shared/i18n/localize"; import {ConnectionStatistics} from "tc-shared/connection/ConnectionBase"; +import {AbstractInput} from "tc-shared/voice/RecorderBase"; +import {crashOnThrow, ignorePromise} from "tc-shared/proto"; export class NativeVoiceConnectionWrapper extends AbstractVoiceConnection { private readonly serverConnectionStateChangedListener; @@ -24,6 +26,9 @@ export class NativeVoiceConnectionWrapper extends AbstractVoiceConnection { private connectionState: VoiceConnectionStatus; private currentRecorder: RecorderProfile; + private ignoreRecorderUnmount: boolean; + private listenerRecorder: (() => void)[]; + private registeredVoiceClients: {[key: number]: NativeVoiceClientWrapper} = {}; private currentlyReplayingAudio = false; @@ -32,6 +37,7 @@ export class NativeVoiceConnectionWrapper extends AbstractVoiceConnection { constructor(connection: ServerConnection, voice: NativeVoiceConnection) { super(connection); this.native = voice; + this.ignoreRecorderUnmount = false; this.serverConnectionStateChangedListener = () => { if(this.connection.getConnectionState() === ConnectionState.CONNECTED) { @@ -78,46 +84,61 @@ export class NativeVoiceConnectionWrapper extends AbstractVoiceConnection { return this.native.decoding_supported(codec); } - async acquireVoiceRecorder(recorder: RecorderProfile | undefined): Promise { - if(this.currentRecorder === recorder) { + async acquireVoiceRecorder(recorder: RecorderProfile | undefined, enforce?: boolean): Promise { + if(this.currentRecorder === recorder && !enforce) { return; } - if(this.currentRecorder) { - this.currentRecorder.callback_unmount = undefined; - this.native.set_audio_source(undefined); + this.listenerRecorder?.forEach(callback => callback()); + this.listenerRecorder = undefined; - this.handleVoiceEndEvent(); - await this.currentRecorder.unmount(); - this.currentRecorder = undefined; + if(this.currentRecorder) { + this.ignoreRecorderUnmount = true; + this.ignoreRecorderUnmount = false; + + this.native.set_audio_source(undefined); } - await recorder?.unmount(); const oldRecorder = recorder; this.currentRecorder = recorder; - try { - if(recorder) { - if(!(recorder.input instanceof NativeInput)) { - this.currentRecorder = undefined; - throw "Recorder input must be an instance of NativeInput!"; + if(this.currentRecorder) { + const connection = this; + await recorder.ownRecorder(new class extends ConnectionRecorderProfileOwner { + getConnection(): ConnectionHandler { + return connection.connection.client; } - recorder.current_handler = this.connection.client; - recorder.callback_unmount = () => { - logDebug(LogCategory.VOICE, tr("Lost voice recorder...")); - this.acquireVoiceRecorder(undefined); - }; + protected handleRecorderInput(input: AbstractInput): any { + if(!(input instanceof NativeInput)) { + logError(LogCategory.VOICE, tr("Recorder input isn't an instance of NativeInput. Ignoring recorder input.")); + return; + } - recorder.callback_start = this.handleVoiceStartEvent.bind(this); - recorder.callback_stop = this.handleVoiceEndEvent.bind(this); + connection.native.set_audio_source(input.getNativeConsumer()); + } - this.native.set_audio_source(recorder.input.getNativeConsumer()); - } - } catch(error) { - this.currentRecorder = undefined; - throw error; + protected handleUnmount(): any { + if(connection.ignoreRecorderUnmount) { + return; + } + + connection.currentRecorder = undefined; + ignorePromise(crashOnThrow(connection.acquireVoiceRecorder(undefined, true))); + } + }); + + this.listenerRecorder = []; + this.listenerRecorder.push(recorder.events.on("notify_voice_start", () => this.handleVoiceStartEvent())); + this.listenerRecorder.push(recorder.events.on("notify_voice_end", () => this.handleVoiceEndEvent(tr("recorder event")))); } + + if(this.currentRecorder?.isInputActive()) { + this.handleVoiceStartEvent(); + } else { + this.handleVoiceEndEvent(tr("recorder change")); + } + this.events.fire("notify_recorder_changed", { oldRecorder, newRecorder: recorder @@ -144,6 +165,7 @@ export class NativeVoiceConnectionWrapper extends AbstractVoiceConnection { if(status === this.currentlyReplayingAudio) { return; } + this.currentlyReplayingAudio = status; this.events.fire("notify_voice_replay_state_change", { replaying: status }); } @@ -161,26 +183,22 @@ export class NativeVoiceConnectionWrapper extends AbstractVoiceConnection { this.native.enable_voice_send(true); this.localAudioStarted = true; - logInfo(LogCategory.VOICE, tr("Local voice started")); - const ch = chandler.getClient(); - if(ch) ch.speaking = true; + logInfo(LogCategory.VOICE, tr("Local voice started")); + chandler.getClient()?.setSpeaking(true); } - private handleVoiceEndEvent() { + private handleVoiceEndEvent(reason: string) { this.native.enable_voice_send(false); + if(!this.localAudioStarted) { + return; + } + const chandler = this.connection.client; - const ch = chandler.getClient(); - if(ch) ch.speaking = false; + chandler.getClient()?.setSpeaking(false); - if(!chandler.connected) - return false; - - if(chandler.isMicrophoneMuted()) - return false; - - logInfo(LogCategory.VOICE, tr("Local voice ended")); + logInfo(LogCategory.VOICE, tr("Local voice ended (%s)"), reason); this.localAudioStarted = false; } @@ -196,12 +214,15 @@ export class NativeVoiceConnectionWrapper extends AbstractVoiceConnection { } unregisterVoiceClient(client: VoiceClient) { - if(!(client instanceof NativeVoiceClientWrapper)) + if(!(client instanceof NativeVoiceClientWrapper)) { throw "invalid client type"; + } delete this.registeredVoiceClients[client.getClientId()]; this.native.unregister_client(client.getClientId()); client.destroy(); + + this.handleVoiceClientStateChange(); } stopAllVoiceReplays() { @@ -221,18 +242,15 @@ export class NativeVoiceConnectionWrapper extends AbstractVoiceConnection { return undefined; } - setWhisperSessionInitializer(initializer: WhisperSessionInitializer | undefined) { - } + setWhisperSessionInitializer(initializer: WhisperSessionInitializer | undefined) { } startWhisper(target: WhisperTarget): Promise { return Promise.resolve(undefined); } - dropWhisperSession(session: WhisperSession) { - } + dropWhisperSession(session: WhisperSession) { } - stopWhisper() { - } + stopWhisper() { } getConnectionStats(): Promise { /* FIXME: This is iffy! */ @@ -276,6 +294,10 @@ class NativeVoiceClientWrapper implements VoiceClient { case PlayerState.STOPPING: this.setState(VoicePlayerState.STOPPING); break; + + default: + logError(LogCategory.VOICE, tr("Native audio player has invalid state: %o"), state); + break; } } diff --git a/modules/renderer/hooks/AudioInput.ts b/modules/renderer/hooks/AudioInput.ts index 1c515f5..6587ec6 100644 --- a/modules/renderer/hooks/AudioInput.ts +++ b/modules/renderer/hooks/AudioInput.ts @@ -17,13 +17,4 @@ setRecorderBackend(new class implements AudioRecorderBacked { getDeviceList(): DeviceList { return inputDeviceList; } - - isRnNoiseSupported(): boolean { - return true; - } - - toggleRnNoise(target: boolean) { - NativeLevelMeter.instances.forEach(input => input.nativeConsumer.toggle_rnnoise(target)); - NativeInput.instances.forEach(input => input.nativeConsumer.toggle_rnnoise(target)); - } }); \ No newline at end of file diff --git a/modules/renderer/hooks/ContextMenu.ts b/modules/renderer/hooks/ContextMenu.ts new file mode 100644 index 0000000..f375391 --- /dev/null +++ b/modules/renderer/hooks/ContextMenu.ts @@ -0,0 +1,11 @@ +import * as loader from "tc-loader"; +import {setGlobalContextMenuFactory} from "tc-shared/ui/ContextMenu"; +import {ClientContextMenuFactory} from "../ContextMenu"; + +loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, { + name: "context menu", + function: async () => { + setGlobalContextMenuFactory(new ClientContextMenuFactory()); + }, + priority: 60 +}); \ No newline at end of file diff --git a/modules/renderer/hooks/ServerSettingsAdapter.ts b/modules/renderer/hooks/ServerSettingsAdapter.ts new file mode 100644 index 0000000..36fbee1 --- /dev/null +++ b/modules/renderer/hooks/ServerSettingsAdapter.ts @@ -0,0 +1,10 @@ +import * as loader from "tc-loader"; +import {Stage} from "tc-loader"; +import {ClientServerSettingsStorage} from "../ClientStorage"; +import {setServerSettingsStorage} from "tc-shared/ServerSettings"; + +loader.register_task(Stage.JAVASCRIPT_INITIALIZING, { + name: "server storage init", + function: async () => setServerSettingsStorage(new ClientServerSettingsStorage()), + priority: 80 +}); \ No newline at end of file diff --git a/modules/renderer/hooks/StorageAdapter.ts b/modules/renderer/hooks/StorageAdapter.ts new file mode 100644 index 0000000..01b1c1c --- /dev/null +++ b/modules/renderer/hooks/StorageAdapter.ts @@ -0,0 +1,15 @@ +import {setStorageAdapter} from "tc-shared/StorageAdapter"; +import {clientStorage, initializeClientStorage} from "../ClientStorage"; +import * as loader from "tc-loader"; +import {Stage} from "tc-loader"; + +loader.register_task(Stage.JAVASCRIPT_INITIALIZING, { + name: "storage init", + function: async () => { + await initializeClientStorage(); + setStorageAdapter(clientStorage); + }, + + /* Must come before everything else! */ + priority: 10_000 +}); \ No newline at end of file diff --git a/modules/renderer/hooks/index.ts b/modules/renderer/hooks/index.ts index 90b5a37..bbacec4 100644 --- a/modules/renderer/hooks/index.ts +++ b/modules/renderer/hooks/index.ts @@ -1,9 +1,12 @@ +import "./StorageAdapter"; +import "./ContextMenu"; import "./AudioInput"; import "./AudioBackend"; import "./Backend"; import "./ChangeLogClient"; import "./Dns"; import "./MenuBar"; +import "./ServerSettingsAdapter"; import "./ServerConnection"; import "./Video"; import "./Sound"; diff --git a/modules/renderer/index.ts b/modules/renderer/index.ts index f3200cd..30ccbca 100644 --- a/modules/renderer/index.ts +++ b/modules/renderer/index.ts @@ -41,26 +41,6 @@ declare global { } } -/* we use out own jquery resource */ -loader.register_task(loader.Stage.JAVASCRIPT, { - name: "teaclient jquery", - function: async () => { - window.$ = require("jquery"); - window.jQuery = window.$; - Object.assign(window.$, window.jsrender = require('jsrender')); - }, - priority: 80 -}); - -loader.register_task(loader.Stage.SETUP, { - name: "teaclient initialize persistent storage", - function: async () => { - const storage = require("./PersistentLocalStorage"); - await storage.initialize(); - }, - priority: 90 -}); - loader.register_task(loader.Stage.INITIALIZING, { name: "teaclient initialize logging", function: async () => { @@ -149,19 +129,16 @@ loader.register_task(loader.Stage.LOADED, { }); -loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, { +loader.register_task(loader.Stage.JAVASCRIPT, { name: "teaclient load adapters", function: async () => { /* all files which replaces a native driver */ try { - await import("./MenuBar"); - await import("./ContextMenu"); + await import("./hooks"); await import("./SingleInstanceHandler"); await import("./IconHelper"); await import("./connection/FileTransfer"); - await import("./hooks"); - await import("./UnloadHandler"); await import("./WindowsTrayHandler"); } catch (error) { @@ -171,5 +148,6 @@ loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, { } remote.getCurrentWindow().on('focus', () => remote.getCurrentWindow().flashFrame(false)); }, - priority: 60 + /* Register all tasks after all javascript files have been loaded */ + priority: -1 }); \ No newline at end of file diff --git a/modules/tsconfig_renderer.json b/modules/tsconfig_renderer.json index 38a0b0d..27559b5 100644 --- a/modules/tsconfig_renderer.json +++ b/modules/tsconfig_renderer.json @@ -11,7 +11,8 @@ "tc-loader": ["imports/loader"], "svg-sprites/*": ["imports/svg-sprites/*"], "tc-events": ["imports/vendor/TeaEventBus/src/index.d.ts"], - "tc-services": ["imports/vendor/TeaClientServices/src/index.d.ts"] + "tc-services": ["imports/vendor/TeaClientServices/src/index.d.ts"], + "tc-native/connection": ["native/serverconnection/exports/exports.d.ts"] } }, "include": [ diff --git a/tsconfig.json b/tsconfig.json index decbd31..6efb6f8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,8 @@ "tc-loader": ["imports/loader"], "svg-sprites/*": ["imports/svg-sprites/*"], "tc-events": ["imports/vendor/TeaEventBus/src/index.d.ts"], - "tc-services": ["imports/vendor/TeaClientServices/src/index.d.ts"] + "tc-services": ["imports/vendor/TeaClientServices/src/index.d.ts"], + "tc-native/connection": ["native/serverconnection/exports/exports.d.ts"] } }, "exclude": [