Finalizing the 1.5.3 client

This commit is contained in:
WolverinDEV 2021-04-19 20:27:12 +02:00
parent 145f1ff0b1
commit 4a19f3a827
99 changed files with 1144 additions and 617 deletions

2
github

@ -1 +1 @@
Subproject commit 35052680fe58de01102dfd4b65835df08c2a7f20
Subproject commit 90d3a6c84c003ab42a3ecfb5197b9368897862b1

View File

@ -45,9 +45,9 @@ export declare class BookmarkManager {
private readonly registeredBookmarks;
private defaultBookmarkCreated;
constructor();
private loadBookmarks;
loadBookmarks(): Promise<void>;
private importOldBookmarks;
private saveBookmarks;
saveBookmarks(): Promise<void>;
getRegisteredBookmarks(): BookmarkEntry[];
getOrderedRegisteredBookmarks(): OrderedBookmarkEntry[];
findBookmark(uniqueId: string): BookmarkEntry | undefined;

View File

@ -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<ConnectionEvents>;
startConnectionNew(parameters: ConnectParameters, autoReconnectAttempt: boolean): Promise<void>;
startConnection(addr: string, profile: ConnectionProfile, user_action: boolean, parameters: ConnectParametersOld): Promise<void>;
@ -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: {};
}

18
imports/shared-app/Mutex.d.ts vendored Normal file
View File

@ -0,0 +1,18 @@
export declare class Mutex<T> {
private value;
private taskExecuting;
private taskQueue;
private freeListener;
constructor(value: T);
isFree(): boolean;
awaitFree(): Promise<void>;
execute<R>(callback: (value: T, setValue: (newValue: T) => void) => R | Promise<R>): Promise<R>;
tryExecute<R>(callback: (value: T, setValue: (newValue: T) => void) => R | Promise<R>): Promise<{
status: "success";
result: R;
} | {
status: "would-block";
}>;
private executeNextTask;
private triggerFinished;
}

View File

@ -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<KeyDescriptor> {
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 {};

View File

@ -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;
}

20
imports/shared-app/ServerSettings.d.ts vendored Normal file
View File

@ -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<V extends RegistryValueType, DV>(key: RegistryKey<V>, defaultValue: DV): V | DV;
getValue<V extends RegistryValueType>(key: ValuedRegistryKey<V>, defaultValue?: V): V;
setValue<T extends RegistryValueType>(key: RegistryKey<T>, 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;

16
imports/shared-app/StorageAdapter.d.ts vendored Normal file
View File

@ -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<boolean>;
get(key: string): Promise<string | null>;
set(key: string, value: string): Promise<void>;
delete(key: string): Promise<void>;
}
export declare function getStorageAdapter(): StorageAdapter;
export declare function setStorageAdapter(adapter: StorageAdapter): void;

View File

@ -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: {

View File

@ -4,8 +4,6 @@ export interface AudioRecorderBacked {
createInput(): AbstractInput;
createLevelMeter(device: InputDevice): Promise<LevelMeter>;
getDeviceList(): DeviceList;
isRnNoiseSupported(): boolean;
toggleRnNoise(target: boolean): any;
}
export interface DeviceListEvents {
notify_list_updated: {

View File

@ -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<void>;
export declare function initializeSounds(): Promise<void>;
export interface PlaybackOptions {
ignore_muted?: boolean;
ignore_overlap?: boolean;

View File

@ -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<ClientInfoResult>;
getInfoByUniqueId(uniqueId: string): Promise<ClientInfoResult>;
executeQueries(): Promise<void>;
}

View File

@ -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<void>;
availableVoiceClients(): VoiceClient[];

View File

@ -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<CommandResult>;
protected sendPluginCommand(data: string, mode: "server" | "view" | "channel" | "private", clientOrChannelId?: number): Promise<CommandResult>;
}
export declare class PluginCmdRegistry {
readonly connection: ConnectionHandler;

View File

@ -97,6 +97,7 @@ export declare class RTCConnection {
restartConnection(): void;
reset(updateConnectionState: boolean): void;
setTrackSource(type: RTCSourceTrackType, source: MediaStreamTrack | null): Promise<MediaStreamTrack>;
clearTrackSources(types: RTCSourceTrackType[]): Promise<MediaStreamTrack[]>;
startVideoBroadcast(type: VideoBroadcastType, config: VideoBroadcastConfig): Promise<void>;
changeVideoBroadcastConfig(type: VideoBroadcastType, config: VideoBroadcastConfig): Promise<void>;
startAudioBroadcast(): Promise<void>;

View File

@ -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";

View File

@ -38,7 +38,8 @@ export declare abstract class ClientAvatar {
setLoading(): void;
setLoaded(data: AvatarStateData["loaded"]): void;
setErrored(data: AvatarStateData["errored"]): void;
awaitLoaded(): Promise<void>;
awaitLoaded(): Promise<true>;
awaitLoaded(timeout: number): Promise<boolean>;
getState(): AvatarState;
getStateData(): AvatarStateData[AvatarState];
getAvatarHash(): string | "unknown";

View File

@ -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";

View File

@ -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<T> = {
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<void>;
private loadAvatarRequest;
loadAvatarImage(serverUniqueId: string, mode: OwnAvatarMode): Promise<LocalAvatarLoadResult<ArrayBuffer>>;
loadAvatar(serverUniqueId: string, mode: OwnAvatarMode, createResourceUrl: boolean): Promise<LocalAvatarLoadResult<LocalAvatarInfo>>;
updateAvatar(serverUniqueId: string, mode: OwnAvatarMode, target: File): Promise<LocalAvatarUpdateResult>;
removeAvatar(serverUniqueId: string, mode: OwnAvatarMode): Promise<void>;
/**
* Move the avatar file which is currently in "uploading" state to server
* @param serverUniqueId
*/
avatarUploadSucceeded(serverUniqueId: string): Promise<void>;
}
export declare function getOwnAvatarStorage(): OwnAvatarStorage;

View File

@ -1,2 +1,6 @@
export declare const downloadTextAsFile: (text: string, name: string) => void;
export declare const requestFile: (options: {
accept?: string;
multiple?: boolean;
}) => Promise<File[]>;
export declare const requestFileAsText: () => Promise<string>;

View File

@ -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 {};

View File

@ -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";

View File

@ -72,3 +72,12 @@ export declare type ReadonlyKeys<T> = {
-readonly [Q in P]: T[P];
}, never, P>;
}[keyof T];
export declare function crashOnThrow<T>(promise: Promise<T> | (() => Promise<T>)): Promise<T>;
export declare function ignorePromise<T>(_promise: Promise<T>): 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;

View File

@ -15,6 +15,8 @@ export interface RegistryKey<ValueType extends RegistryValueType> {
export interface ValuedRegistryKey<ValueType extends RegistryValueType> extends RegistryKey<ValueType> {
defaultValue: ValueType;
}
export declare function encodeSettingValueToString<T extends RegistryValueType>(input: T): string;
export declare function resolveSettingKey<ValueType extends RegistryValueType, DefaultType>(key: RegistryKey<ValueType>, 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<string>;
const KEY_LOAD_DUMMY_ERROR: ValuedRegistryKey<boolean>;
}
export declare class StaticSettings {
private static _instance;
static get instance(): StaticSettings;
protected staticValues: {};
protected constructor(_reserved?: any);
static<V extends RegistryValueType, DV>(key: RegistryKey<V>, defaultValue: DV): V | DV;
static<V extends RegistryValueType>(key: ValuedRegistryKey<V>, 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<number>;
static readonly KEY_MICROPHONE_THRESHOLD_RELEASE_SMOOTH: ValuedRegistryKey<number>;
static readonly KEY_MICROPHONE_THRESHOLD_RELEASE_DELAY: ValuedRegistryKey<number>;
static readonly KEY_SPEAKER_DEVICE_ID: RegistryKey<string>;
static readonly KEY_UPDATER_LAST_USED_UI: RegistryKey<string>;
static readonly KEY_UPDATER_LAST_USED_CLIENT: RegistryKey<string>;
static readonly FN_LOG_ENABLED: (category: string) => RegistryKey<boolean>;
static readonly FN_SEPARATOR_STATE: (separator: string) => RegistryKey<string>;
static readonly FN_LOG_LEVEL_ENABLED: (category: string) => RegistryKey<boolean>;
@ -159,30 +156,18 @@ export declare class Settings {
static readonly FN_EVENTS_LOG_ENABLED: (event: string) => RegistryKey<boolean>;
static readonly FN_EVENTS_FOCUS_ENABLED: (event: string) => RegistryKey<boolean>;
static readonly KEYS: any[];
static initialize(): void;
readonly events: Registry<SettingsEvents>;
private readonly cacheGlobal;
private settingsCache;
private saveWorker;
private updated;
private saveState;
constructor();
initialize(): Promise<void>;
getValue<V extends RegistryValueType, DV>(key: RegistryKey<V>, defaultValue: DV): V | DV;
getValue<V extends RegistryValueType>(key: ValuedRegistryKey<V>, defaultValue?: V): V;
setValue<T extends RegistryValueType>(key: RegistryKey<T>, value?: T): void;
globalChangeListener<T extends RegistryValueType>(key: RegistryKey<T>, 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<V extends RegistryValueType, DV>(key: RegistryKey<V>, defaultValue: DV): V | DV;
getValue<V extends RegistryValueType>(key: ValuedRegistryKey<V>, defaultValue?: V): V;
setValue<T extends RegistryValueType>(key: RegistryKey<T>, value?: T): void;
setServer(server_unique_id: string): void;
private doSave;
save(): void;
}
export declare let settings: Settings;

View File

@ -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<string>;

View File

@ -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<ClientProperties>;
client_properties: ClientProperties;
};
notify_mute_state_change: {
@ -166,8 +164,10 @@ export declare class ClientEntry<Events extends ClientEvents = ClientEvents> 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<Events extends ClientEvents = ClientEvents> 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<boolean>;
openRenameModal(): void;

View File

@ -28,6 +28,7 @@ declare class InfoController {
sendMicrophoneState(): void;
sendMicrophoneList(): void;
sendSpeakerState(): void;
sendSpeakerList(): Promise<void>;
sendSubscribeState(): void;
sendQueryState(): void;
sendHostButton(): void;

View File

@ -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;
};

View File

@ -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<DropdownEntry>[];
children?: React.ReactElement<DropdownEntry | DropdownTitleEntry>[];
}
export declare class DropdownEntry extends ReactComponentBase<DropdownEntryProperties, {}> {
protected defaultState(): {};
export declare class DropdownEntry extends React.PureComponent<DropdownEntryProperties> {
render(): JSX.Element;
}
export declare class DropdownTitleEntry extends React.PureComponent<{
children: any;
}> {
render(): JSX.Element;
}
export declare const DropdownContainer: (props: {

View File

@ -15,7 +15,6 @@ export declare abstract class AbstractConversationController<Events extends Abst
protected registerConversationManagerEvents(manager: Manager): void;
protected registerConversationEvents(conversation: ConversationType): void;
protected setCurrentlySelected(conversation: ConversationType | undefined): void;
handlePanelShow(): void;
protected reportStateToUI(conversation: AbstractChat<any>): void;
uiQueryHistory(conversation: AbstractChat<any>, timestamp: number, enforce?: boolean): void;
protected getCurrentConversation(): ConversationType | undefined;

View File

@ -121,7 +121,6 @@ export interface AbstractConversationUiEvents {
notify_selected_chat: {
chatId: "unselected" | string;
};
notify_panel_show: {};
notify_chat_event: {
chatId: string;
triggerUnread: boolean;

View File

@ -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;
}

View File

@ -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;

View File

@ -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[];

View File

@ -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;

View File

@ -1 +0,0 @@
export declare function spawnAvatarUpload(callback_data: (data: ArrayBuffer | undefined | null) => any): void;

View File

@ -1,6 +0,0 @@
export declare const Regex: {
DOMAIN: RegExp;
IP_V4: RegExp;
IP_V6: RegExp;
IP: RegExp;
};

View File

@ -1,2 +0,0 @@
import { ClientEntry } from "../../tree/Client";
export declare function createServerGroupAssignmentModal(client: ClientEntry, callback: (groups: number[], flag: boolean) => Promise<boolean>): void;

View File

@ -0,0 +1 @@
export declare function spawnAboutModal(): void;

View File

@ -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;
};
}

View File

@ -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<ModalAboutEvents>, variables: IpcVariableDescriptor<ModalAboutVariables>);
renderBody(): React.ReactElement;
renderTitle(): string | React.ReactElement;
}
export default Modal;

View File

@ -0,0 +1,2 @@
import { ConnectionHandler } from "tc-shared/ConnectionHandler";
export declare function spawnAvatarUpload(connection: ConnectionHandler): void;

View File

@ -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;
};
}

View File

@ -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<ModalAvatarUploadEvents>, variables: IpcVariableDescriptor<ModalAvatarUploadVariables>, serverUniqueId: string);
protected onDestroy(): void;
renderBody(): React.ReactElement;
renderTitle(): string | React.ReactElement;
}
export default ModalAvatarUpload;

View File

@ -0,0 +1,2 @@
import { ConnectionHandler } from "tc-shared/ConnectionHandler";
export declare function spawnServerGroupAssignments(handler: ConnectionHandler, targetClientDatabaseId: number): void;

View File

@ -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;
};
};
}

View File

@ -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<ModalClientGroupAssignmentEvents>, variables: IpcVariableDescriptor<ModalClientGroupAssignmentVariables>);
protected onDestroy(): void;
renderBody(): React.ReactElement;
renderTitle(): string | React.ReactElement;
}

View File

@ -0,0 +1,2 @@
import { InputProcessor } from "tc-shared/voice/RecorderBase";
export declare function spawnInputProcessorModal(processor: InputProcessor): void;

View File

@ -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;
};
}

View File

@ -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<ModalInputProcessorEvents>, variables: IpcVariableDescriptor<ModalInputProcessorVariables>);
protected onDestroy(): void;
renderBody(): React.ReactElement;
renderTitle(): string | React.ReactElement;
}
export default Modal;

View File

@ -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<PermissionEditorEvents>;
}
interface PermissionEditorState {
state: "no-permissions" | "unset" | "normal";
}
export declare class PermissionEditor extends React.Component<PermissionEditorProperties, PermissionEditorState> {
render(): JSX.Element[];
componentDidMount(): void;
}
export {};

View File

@ -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<PermissionEditorEvents>;
}
interface PermissionEditorState {
state: "no-permissions" | "unset" | "normal";
}
export declare class EditorRenderer extends React.Component<PermissionEditorProperties, PermissionEditorState> {
render(): JSX.Element;
componentDidMount(): void;
}
export {};

View File

@ -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;

View File

@ -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;

View File

@ -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<PermissionModalEvents>;
readonly editorEvents: Registry<PermissionEditorEvents>;
constructor(serverInfo: PermissionEditorServerInfo, modalEvents: IpcRegistryDescription<PermissionModalEvents>, editorEvents: IpcRegistryDescription<PermissionEditorEvents>);
protected onDestroy(): void;
renderBody(): JSX.Element;
renderTitle(): React.ReactElement<Translatable>;
}
export default PermissionEditorModal;

View File

@ -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<PermissionModalEvents>;
editorEvents: Registry<PermissionEditorEvents>;
}, {}> {
render(): JSX.Element[];
}

View File

@ -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<DeviceListState, "error">;
};
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<MicrophoneSettingsEvents>): void;

View File

@ -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<DeviceListState, "error">;
};
notify_input_level: {
level: InputDeviceLevel;
};
notify_highlight: {
field: "hs-0" | "hs-1" | "hs-2" | undefined;
};
notify_destroy: {};
}

View File

@ -1,6 +1,6 @@
/// <reference types="react" />
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<MicrophoneSettingsEvents>;
}) => JSX.Element;

View File

@ -32,6 +32,7 @@ export declare class NavigationBar extends ReactComponentBase<NavigationBarPrope
private ignoreBlur;
private lastSucceededPath;
protected defaultState(): NavigationBarState;
componentDidMount(): void;
render(): JSX.Element;
componentDidUpdate(prevProps: Readonly<NavigationBarProperties>, prevState: Readonly<NavigationBarState>, snapshot?: any): void;
private onPathClicked;

View File

@ -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;

View File

@ -0,0 +1,6 @@
/// <reference types="react" />
export declare const Arrow: (props: {
direction: NavigationReason;
className?: string;
onClick?: () => void;
}) => JSX.Element;

View File

@ -6,6 +6,7 @@ export interface CheckboxProperties {
onChange?: (value: boolean) => void;
value?: boolean;
initialValue?: boolean;
className?: string;
children?: never;
}
export interface CheckboxState {

View File

@ -1,5 +1,5 @@
/// <reference types="react" />
export declare const CountryIcon: (props: {
country: string;
export declare const CountryCode: (props: {
alphaCode: string;
className?: string;
}) => JSX.Element;

View File

@ -1,5 +1,5 @@
/// <reference types="react" />
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>;

View File

@ -26,3 +26,4 @@ export declare const IconTooltip: (props: {
className?: string;
outerClassName?: string;
}) => JSX.Element;
export declare const TooltipHook: React.MemoExoticComponent<() => JSX.Element>;

View File

@ -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<ModalInstanceEvents>;
show(): Promise<void>;
hide(): Promise<void>;
minimize(): Promise<void>;
maximize(): Promise<void>;
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<ModalBookmarkEvents>, IpcVariableDescriptor<ModalBookmarkVariables>];
"modal-bookmark-add-server": [IpcRegistryDescription<ModalBookmarksAddServerEvents>, IpcVariableDescriptor<ModalBookmarksAddServerVariables>];
"modal-poked": [IpcRegistryDescription<ModalPokeEvents>, IpcVariableDescriptor<ModalPokeVariables>];
"modal-assign-server-groups": [IpcRegistryDescription<ModalClientGroupAssignmentEvents>, IpcVariableDescriptor<ModalClientGroupAssignmentVariables>];
"modal-permission-edit": [PermissionEditorServerInfo, IpcRegistryDescription<PermissionModalEvents>, IpcRegistryDescription<PermissionEditorEvents>];
"modal-avatar-upload": [IpcRegistryDescription<ModalAvatarUploadEvents>, IpcVariableDescriptor<ModalAvatarUploadVariables>, string];
"modal-input-processor": [IpcRegistryDescription<ModalInputProcessorEvents>, IpcVariableDescriptor<ModalInputProcessorVariables>];
"modal-about": [IpcRegistryDescription, IpcVariableDescriptor<ModalAboutVariables>];
}

View File

@ -33,7 +33,6 @@ export declare class PageModalRenderer extends React.PureComponent<{
modalInstance: AbstractModal;
onBackdropClicked: () => void;
children: React.ReactElement<ModalFrameRenderer>;
}, {
shown: boolean;
}> {
constructor(props: any);

View File

@ -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<void>;
hide(): Promise<void>;
minimize(): Promise<void>;
maximize(): Promise<void>;
private mutateWindow;
private handleWindowDestroyed;
private registerIpcMessageHandler;

View File

@ -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<ModalInstanceEvents>;
readonly refRendererInstance: React.RefObject<InternalRendererInstance>;
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<any>, constructorArguments: any[], modalOptions: ModalOptions);
private constructModal;
private destructModal;
private destructModalInstance;
getState(): ModalState;
getEvents(): Registry<ModalInstanceEvents>;
show(): Promise<void>;
hide(): Promise<void>;
minimize(): Promise<void>;
maximize(): Promise<void>;
destroy(): void;
private getCloseCallback;
private getPopoutCallback;
private getMinimizeCallback;
getCloseCallback(): () => void;
getPopoutCallback(): () => void;
getMinimizeCallback(): any;
}
export declare const InternalModalHook: React.MemoExoticComponent<() => JSX.Element>;
export {};

View File

@ -209,9 +209,6 @@ export interface ChannelTreeUIEvents {
treeEntryId: number;
unread: boolean;
};
notify_visibility_changed: {
visible: boolean;
};
notify_destroy: {};
}
export declare type ChannelTreeDragEntry = {

View File

@ -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 {};

View File

@ -32,7 +32,6 @@ export declare class ChannelTreeView extends ReactComponentBase<ChannelTreeViewP
constructor(props: any);
componentDidMount(): void;
componentWillUnmount(): void;
private handleVisibilityChanged;
private visibleEntries;
render(): JSX.Element;
private onScroll;

View File

@ -20,6 +20,7 @@ export declare abstract class UiVariableProvider<Variables extends UiVariableMap
getArtificialDelay(): number;
setArtificialDelay(value: number): void;
setVariableProvider<T extends keyof Variables>(variable: T, provider: (customData: any) => Variables[T] | Promise<Variables[T]>): void;
setVariableProviderAsync<T extends keyof Variables>(variable: T, provider: (customData: any) => Promise<Variables[T]>): void;
/**
* @param variable
* @param editor If the editor returns `false` or a new variable, such variable will be used

View File

@ -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;
}

View File

@ -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<T extends InputProcessorType>(processor: T): InputProcessorConfigMapping[T];
/**
* Apply the target config.
* @param processor
* @param config
*/
applyProcessorConfig<T extends InputProcessorType>(processor: T, config: InputProcessorConfigMapping[T]): any;
/**
* Get the current processor statistics.
*/
getStatistics(): InputProcessorStatistics;
}
export interface AbstractInput {
readonly events: Registry<InputEvents>;
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;

View File

@ -29,6 +29,27 @@ export declare const defaultRecorderEvents: Registry<DefaultRecorderEvents>;
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<RecorderProfileEvents>;
@ -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<void>;
getOwner(): RecorderProfileOwner | undefined;
isInputActive(): boolean;
/** @deprecated use `ownRecorder(undefined)` */
unmount(): Promise<void>;
getVadType(): VadType;
setVadType(type: VadType): boolean;

View File

@ -123,5 +123,5 @@ function deploy_client() {
#install_npm
#compile_scripts
#compile_native
package_client
#package_client
deploy_client

View File

@ -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");
}
})
});
}

View File

@ -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<void> {
return fs.remove(storageKeyPath(key)).catch(error => {});
}
async get(key: string): Promise<string | null> {
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<boolean> {
return fs.pathExists(storageKeyPath(key));
}
set(key: string, value: string): Promise<void> {
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();
}

View File

@ -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<ChangeLog> {
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<ChangeLog> {
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());
}
}

View File

@ -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);
}
});
};

View File

@ -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);

View File

@ -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();
});

View File

@ -25,6 +25,10 @@ export class NativeWindowManager implements WindowManager {
constructor() {
this.windowInstances = {};
this.events = new Registry<WindowManagerEvents>();
window.onunload = () => {
Object.values(this.windowInstances).forEach(window => window.destroy());
};
}
getEvents(): Registry<WindowManagerEvents> {

View File

@ -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<InputEvents>;
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<InputEvents>();
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<T extends InputProcessorType>(processor: T, config: Partial<InputProcessorConfigMapping[T]>) {
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<T extends InputProcessorType>(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);
}
});
}
}

View File

@ -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);

View File

@ -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<void> {
if(this.currentRecorder === recorder) {
async acquireVoiceRecorder(recorder: RecorderProfile | undefined, enforce?: boolean): Promise<void> {
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<void> {
return Promise.resolve(undefined);
}
dropWhisperSession(session: WhisperSession) {
}
dropWhisperSession(session: WhisperSession) { }
stopWhisper() {
}
stopWhisper() { }
getConnectionStats(): Promise<ConnectionStatistics> {
/* 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;
}
}

View File

@ -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));
}
});

View File

@ -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
});

View File

@ -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
});

View File

@ -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
});

View File

@ -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";

View File

@ -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
});

View File

@ -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": [

View File

@ -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": [