Adding W2G support for the native client
parent
ed94f6dcd2
commit
4b7cd15a24
|
@ -1,4 +1,8 @@
|
|||
# Changelog:
|
||||
* **08.08.20**
|
||||
- Added a watch to gether mode
|
||||
- Added API support for the popout able browsers for the native client
|
||||
|
||||
* **05.08.20**
|
||||
- Putting the CSS files within the assets. No extra load needed any more
|
||||
- Revoked the async file loading limit
|
||||
|
|
|
@ -129,6 +129,11 @@ export function finished() {
|
|||
export function running() { return typeof(currentStage) !== "undefined"; }
|
||||
|
||||
export function register_task(stage: Stage, task: Task) {
|
||||
if(!task.function) {
|
||||
debugger;
|
||||
throw "tried to register a loader task without a function";
|
||||
}
|
||||
|
||||
if(currentStage > stage) {
|
||||
if(config.error)
|
||||
console.warn("Register loading task, but it had already been finished. Executing task anyways!");
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import "./shared";
|
||||
import * as loader from "../loader/loader";
|
||||
import {ApplicationLoader, config, SourcePath} from "../loader/loader";
|
||||
import {ApplicationLoader, SourcePath} from "../loader/loader";
|
||||
import {script_name} from "../loader/utils";
|
||||
import {loadManifest, loadManifestTarget} from "../maifest";
|
||||
|
||||
|
@ -10,8 +10,6 @@ declare global {
|
|||
}
|
||||
}
|
||||
|
||||
const node_require: typeof require = window.require;
|
||||
|
||||
function cache_tag() {
|
||||
const ui = ui_version();
|
||||
return "?_ts=" + (!!ui && ui !== "unknown" ? ui : Date.now());
|
||||
|
@ -168,18 +166,18 @@ loader.register_task(loader.Stage.SETUP, {
|
|||
export default class implements ApplicationLoader {
|
||||
execute() {
|
||||
/* TeaClient */
|
||||
if(node_require) {
|
||||
if(window.require) {
|
||||
if(__build.target !== "client") {
|
||||
loader.critical_error("App seems not to be compiled for the client.", "This app has been compiled for " + __build.target);
|
||||
return;
|
||||
}
|
||||
window.native_client = true;
|
||||
|
||||
const path = node_require("path");
|
||||
const remote = node_require('electron').remote;
|
||||
const path = __non_webpack_require__("path");
|
||||
const remote = __non_webpack_require__('electron').remote;
|
||||
|
||||
const render_entry = path.join(remote.app.getAppPath(), "/modules/", "renderer");
|
||||
const render = node_require(render_entry);
|
||||
const render = __non_webpack_require__(render_entry);
|
||||
|
||||
loader.register_task(loader.Stage.INITIALIZING, {
|
||||
name: "teaclient initialize",
|
||||
|
|
|
@ -5,6 +5,8 @@ import {loadManifest, loadManifestTarget} from "../maifest";
|
|||
import {getUrlParameter} from "../loader/utils";
|
||||
|
||||
export default class implements ApplicationLoader {
|
||||
|
||||
|
||||
execute() {
|
||||
loader.register_task(Stage.SETUP, {
|
||||
function: async taskId => {
|
||||
|
@ -27,24 +29,28 @@ export default class implements ApplicationLoader {
|
|||
name: "page setup",
|
||||
function: async () => {
|
||||
const body = document.body;
|
||||
|
||||
/* top menu */
|
||||
{
|
||||
const container = document.createElement("div");
|
||||
container.setAttribute('id', "top-menu-bar");
|
||||
body.append(container);
|
||||
}
|
||||
|
||||
/* template containers */
|
||||
{
|
||||
const container = document.createElement("div");
|
||||
container.setAttribute('id', "templates");
|
||||
body.append(container);
|
||||
}
|
||||
|
||||
/* sounds container */
|
||||
{
|
||||
const container = document.createElement("div");
|
||||
container.setAttribute('id', "sounds");
|
||||
body.append(container);
|
||||
}
|
||||
|
||||
/* mouse move container */
|
||||
{
|
||||
const container = document.createElement("div");
|
||||
|
@ -52,6 +58,7 @@ export default class implements ApplicationLoader {
|
|||
|
||||
body.append(container);
|
||||
}
|
||||
|
||||
/* tooltip container */
|
||||
{
|
||||
const container = document.createElement("div");
|
||||
|
@ -78,6 +85,26 @@ export default class implements ApplicationLoader {
|
|||
priority: 10
|
||||
});
|
||||
|
||||
if(__build.target === "client") {
|
||||
loader.register_task(Stage.SETUP, {
|
||||
name: "native setup",
|
||||
function: async () => {
|
||||
const path = __non_webpack_require__("path");
|
||||
const remote = __non_webpack_require__('electron').remote;
|
||||
|
||||
const render_entry = path.join(remote.app.getAppPath(), "/modules/", "renderer-manifest", "index");
|
||||
const render = __non_webpack_require__(render_entry);
|
||||
|
||||
loader.register_task(loader.Stage.SETUP, {
|
||||
name: "teaclient setup",
|
||||
function: async () => await render.initialize(getUrlParameter("chunk")),
|
||||
priority: 40
|
||||
});
|
||||
},
|
||||
priority: 50
|
||||
});
|
||||
}
|
||||
|
||||
loader.execute_managed();
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@ $setup-time: 80s / 24; /* 24 frames / sec; the initial sequence is 80 seconds */
|
|||
|
||||
user-select: none;
|
||||
|
||||
z-index: 10000;
|
||||
z-index: 10000000;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#overlay-no-js, #critical-load {
|
||||
z-index: 10000;
|
||||
z-index: 100000000;
|
||||
display: none;
|
||||
position: fixed;
|
||||
|
||||
|
|
|
@ -21,13 +21,13 @@ export class SingletonEvent implements Event<SingletonEvents, "singletone-instan
|
|||
as<T extends keyof SingletonEvents>() : SingletonEvents[T] { return; }
|
||||
}
|
||||
|
||||
export interface EventReceiver<Events = { [key: string]: any }> {
|
||||
export interface EventReceiver<Events extends { [key: string]: any } = { [key: string]: any }> {
|
||||
fire<T extends keyof Events>(event_type: T, data?: Events[T], overrideTypeKey?: boolean);
|
||||
fire_async<T extends keyof Events>(event_type: T, data?: Events[T], callback?: () => void);
|
||||
}
|
||||
|
||||
const event_annotation_key = guid();
|
||||
export class Registry<Events = { [key: string]: any }> implements EventReceiver<Events> {
|
||||
export class Registry<Events extends { [key: string]: any } = { [key: string]: any }> implements EventReceiver<Events> {
|
||||
private readonly registryUuid;
|
||||
|
||||
private handler: {[key: string]: ((event) => void)[]} = {};
|
||||
|
@ -309,12 +309,6 @@ export function ReactEventHandler<ObjectClass = React.Component<any, any>, Event
|
|||
}
|
||||
}
|
||||
|
||||
export namespace sidebar {
|
||||
export interface music {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export namespace modal {
|
||||
export type BotStatusType = "name" | "description" | "volume" | "country_code" | "channel_commander" | "priority_speaker";
|
||||
export type PlaylistStatusType = "replay_mode" | "finished" | "delete_played" | "max_size" | "notify_song_change";
|
||||
|
|
|
@ -2,7 +2,6 @@ import * as log from "tc-shared/log";
|
|||
import {LogCategory} from "tc-shared/log";
|
||||
import {guid} from "tc-shared/crypto/uid";
|
||||
import {Settings, StaticSettings} from "tc-shared/settings";
|
||||
import {createErrorModal} from "tc-shared/ui/elements/Modal";
|
||||
import * as loader from "tc-loader";
|
||||
import {formatMessage, formatMessageString} from "tc-shared/ui/frames/chat";
|
||||
|
||||
|
@ -309,7 +308,9 @@ export async function initialize() {
|
|||
} catch (error) {
|
||||
console.error(tr("Failed to initialize selected translation: %o"), error);
|
||||
const show_error = () => {
|
||||
createErrorModal(tr("Translation System"), tra("Failed to load current selected translation file.{:br:}File: {0}{:br:}Error: {1}{:br:}{:br:}Using default fallback translations.", cfg.current_translation_url, error)).open()
|
||||
import("../ui/elements/Modal").then(Modal => {
|
||||
Modal.createErrorModal(tr("Translation System"), tra("Failed to load current selected translation file.{:br:}File: {0}{:br:}Error: {1}{:br:}{:br:}Using default fallback translations.", cfg.current_translation_url, error)).open()
|
||||
});
|
||||
};
|
||||
if(loader.running())
|
||||
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
||||
|
|
|
@ -152,6 +152,27 @@ export function error(category: LogCategory, message: string, ...optionalParams:
|
|||
log(LogType.ERROR, category, message, ...optionalParams);
|
||||
}
|
||||
|
||||
/* methods for direct import */
|
||||
export function logTrace(category: LogCategory, message: string, ...optionalParams: any[]) {
|
||||
log(LogType.TRACE, category, message, ...optionalParams);
|
||||
}
|
||||
|
||||
export function logDebug(category: LogCategory, message: string, ...optionalParams: any[]) {
|
||||
log(LogType.DEBUG, category, message, ...optionalParams);
|
||||
}
|
||||
|
||||
export function logInfo(category: LogCategory, message: string, ...optionalParams: any[]) {
|
||||
log(LogType.INFO, category, message, ...optionalParams);
|
||||
}
|
||||
|
||||
export function logWarn(category: LogCategory, message: string, ...optionalParams: any[]) {
|
||||
log(LogType.WARNING, category, message, ...optionalParams);
|
||||
}
|
||||
|
||||
export function logError(category: LogCategory, message: string, ...optionalParams: any[]) {
|
||||
log(LogType.ERROR, category, message, ...optionalParams);
|
||||
}
|
||||
|
||||
export function group(level: LogType, category: LogCategory, name: string, ...optionalParams: any[]) : Group {
|
||||
name = "[%s] " + name;
|
||||
optionalParams.unshift(category_mapping.get(category));
|
||||
|
|
|
@ -284,12 +284,14 @@ export function format_time(time: number, default_value: string) {
|
|||
return result.length > 0 ? result.substring(1) : default_value;
|
||||
}
|
||||
|
||||
let _icon_size_style: JQuery<HTMLStyleElement>;
|
||||
let _icon_size_style: HTMLStyleElement;
|
||||
export function set_icon_size(size: string) {
|
||||
if(!_icon_size_style)
|
||||
_icon_size_style = $.spawn("style").appendTo($("#style"));
|
||||
if(!_icon_size_style) {
|
||||
_icon_size_style = document.createElement("style");
|
||||
document.head.append(_icon_size_style);
|
||||
}
|
||||
|
||||
_icon_size_style.text("\n" +
|
||||
_icon_size_style.innerText = ("\n" +
|
||||
".chat-emoji {\n" +
|
||||
" height: " + size + "!important;\n" +
|
||||
" width: " + size + "!important;\n" +
|
||||
|
|
|
@ -2,7 +2,6 @@ import * as log from "tc-shared/log";
|
|||
import {LogCategory} from "tc-shared/log";
|
||||
import * as ipc from "tc-shared/ipc/BrowserIPC";
|
||||
import {ChannelMessage} from "tc-shared/ipc/BrowserIPC";
|
||||
import {spawnYesNo} from "tc-shared/ui/modal/ModalYesNo";
|
||||
import {Registry} from "tc-shared/events";
|
||||
import {
|
||||
EventControllerBase,
|
||||
|
@ -11,21 +10,17 @@ import {
|
|||
} from "tc-shared/ui/react-elements/external-modal/IPCMessage";
|
||||
import {ModalController, ModalEvents, ModalOptions, ModalState} from "tc-shared/ui/react-elements/Modal";
|
||||
|
||||
export class ExternalModalController extends EventControllerBase<"controller"> implements ModalController {
|
||||
export abstract class AbstractExternalModalController extends EventControllerBase<"controller"> implements ModalController {
|
||||
public readonly modalType: string;
|
||||
public readonly userData: any;
|
||||
|
||||
private modalState: ModalState = ModalState.DESTROYED;
|
||||
private readonly modalEvents: Registry<ModalEvents>;
|
||||
private modalState: ModalState = ModalState.DESTROYED;
|
||||
|
||||
private currentWindow: Window;
|
||||
private readonly documentUnloadListener: () => void;
|
||||
private callbackWindowInitialized: (error?: string) => void;
|
||||
|
||||
private readonly documentQuitListener: () => void;
|
||||
private windowClosedTestInterval: number = 0;
|
||||
private windowClosedTimeout: number;
|
||||
|
||||
constructor(modal: string, localEventRegistry: Registry<any>, userData: any) {
|
||||
protected constructor(modal: string, localEventRegistry: Registry, userData: any) {
|
||||
super(localEventRegistry);
|
||||
|
||||
this.modalEvents = new Registry<ModalEvents>();
|
||||
|
@ -36,7 +31,7 @@ export class ExternalModalController extends EventControllerBase<"controller"> i
|
|||
this.ipcChannel = ipc.getInstance().createChannel();
|
||||
this.ipcChannel.messageHandler = this.handleIPCMessage.bind(this);
|
||||
|
||||
this.documentQuitListener = () => this.currentWindow?.close();
|
||||
this.documentUnloadListener = () => this.destroy();
|
||||
}
|
||||
|
||||
getOptions(): Readonly<ModalOptions> {
|
||||
|
@ -51,66 +46,21 @@ export class ExternalModalController extends EventControllerBase<"controller"> i
|
|||
return this.modalState;
|
||||
}
|
||||
|
||||
private trySpawnWindow() : Window | null {
|
||||
const parameters = {
|
||||
"loader-target": "manifest",
|
||||
"chunk": "modal-external",
|
||||
"modal-target": this.modalType,
|
||||
"ipc-channel": this.ipcChannel.channelId,
|
||||
"ipc-address": ipc.getInstance().getLocalAddress(),
|
||||
"disableGlobalContextMenu": __build.mode === "debug" ? 1 : 0,
|
||||
"loader-abort": __build.mode === "debug" ? 1 : 0,
|
||||
};
|
||||
|
||||
const features = {
|
||||
status: "no",
|
||||
location: "no",
|
||||
toolbar: "no",
|
||||
menubar: "no",
|
||||
/*
|
||||
width: 600,
|
||||
height: 400
|
||||
*/
|
||||
};
|
||||
|
||||
let baseUrl = location.origin + location.pathname + "?";
|
||||
return window.open(
|
||||
baseUrl + Object.keys(parameters).map(e => e + "=" + encodeURIComponent(parameters[e])).join("&"),
|
||||
this.modalType,
|
||||
Object.keys(features).map(e => e + "=" + features[e]).join(",")
|
||||
);
|
||||
}
|
||||
protected abstract spawnWindow() : Promise<boolean>;
|
||||
protected abstract focusWindow() : void;
|
||||
protected abstract destroyWindow() : void;
|
||||
|
||||
async show() {
|
||||
if(this.currentWindow) {
|
||||
this.currentWindow.focus();
|
||||
if(this.modalState === ModalState.SHOWN) {
|
||||
this.focusWindow();
|
||||
return;
|
||||
}
|
||||
this.modalState = ModalState.SHOWN;
|
||||
|
||||
this.currentWindow = this.trySpawnWindow();
|
||||
if(!this.currentWindow) {
|
||||
await new Promise((resolve, reject) => {
|
||||
spawnYesNo(tr("Would you like to open the popup?"), tra("Would you like to open popup {}?", this.modalType), callback => {
|
||||
if(!callback) {
|
||||
reject("user aborted");
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentWindow = this.trySpawnWindow();
|
||||
if(this.currentWindow) {
|
||||
reject(tr("Failed to spawn window"));
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
}).close_listener.push(() => reject(tr("user aborted")));
|
||||
})
|
||||
}
|
||||
|
||||
if(!this.currentWindow) {
|
||||
/* some shitty popup blocker or whatever */
|
||||
if(!await this.spawnWindow()) {
|
||||
this.modalState = ModalState.DESTROYED;
|
||||
throw tr("failed to create window");
|
||||
}
|
||||
window.addEventListener("unload", this.documentQuitListener);
|
||||
|
||||
try {
|
||||
await new Promise((resolve, reject) => {
|
||||
|
@ -126,50 +76,25 @@ export class ExternalModalController extends EventControllerBase<"controller"> i
|
|||
};
|
||||
});
|
||||
} catch (e) {
|
||||
this.currentWindow?.close();
|
||||
this.currentWindow = undefined;
|
||||
this.modalState = ModalState.DESTROYED;
|
||||
this.doDestroyWindow();
|
||||
throw e;
|
||||
}
|
||||
|
||||
this.currentWindow.onbeforeunload = () => {
|
||||
clearInterval(this.windowClosedTestInterval);
|
||||
|
||||
this.windowClosedTimeout = Date.now() + 5000;
|
||||
this.windowClosedTestInterval = setInterval(() => {
|
||||
if(!this.currentWindow) {
|
||||
clearInterval(this.windowClosedTestInterval);
|
||||
this.windowClosedTestInterval = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.currentWindow.closed || Date.now() > this.windowClosedTimeout) {
|
||||
window.removeEventListener("unload", this.documentQuitListener);
|
||||
this.currentWindow = undefined;
|
||||
this.destroy(); /* TODO: Test if we should do this */
|
||||
}
|
||||
}, 100);
|
||||
};
|
||||
|
||||
this.modalState = ModalState.SHOWN;
|
||||
window.addEventListener("unload", this.documentUnloadListener);
|
||||
this.modalEvents.fire("open");
|
||||
}
|
||||
|
||||
private destroyPopUp() {
|
||||
if(this.currentWindow) {
|
||||
clearInterval(this.windowClosedTestInterval);
|
||||
this.windowClosedTestInterval = 0;
|
||||
|
||||
window.removeEventListener("beforeunload", this.documentQuitListener);
|
||||
this.currentWindow.close();
|
||||
this.currentWindow = undefined;
|
||||
}
|
||||
private doDestroyWindow() {
|
||||
this.destroyWindow();
|
||||
window.removeEventListener("beforeunload", this.documentUnloadListener);
|
||||
}
|
||||
|
||||
async hide() {
|
||||
if(this.modalState == ModalState.DESTROYED || this.modalState === ModalState.HIDDEN)
|
||||
return;
|
||||
|
||||
this.destroyPopUp();
|
||||
this.doDestroyWindow();
|
||||
this.modalState = ModalState.HIDDEN;
|
||||
this.modalEvents.fire("close");
|
||||
}
|
||||
|
@ -178,7 +103,7 @@ export class ExternalModalController extends EventControllerBase<"controller"> i
|
|||
if(this.modalState === ModalState.DESTROYED)
|
||||
return;
|
||||
|
||||
this.destroyPopUp();
|
||||
this.doDestroyWindow();
|
||||
if(this.ipcChannel)
|
||||
ipc.getInstance().deleteChannel(this.ipcChannel);
|
||||
|
||||
|
@ -187,6 +112,10 @@ export class ExternalModalController extends EventControllerBase<"controller"> i
|
|||
this.modalEvents.fire("destroy");
|
||||
}
|
||||
|
||||
protected handleWindowClosed() {
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
protected handleIPCMessage(remoteId: string, broadcast: boolean, message: ChannelMessage) {
|
||||
if(broadcast)
|
||||
return;
|
||||
|
@ -195,14 +124,6 @@ export class ExternalModalController extends EventControllerBase<"controller"> i
|
|||
log.debug(LogCategory.IPC, tr("Remote window connected with id %s"), remoteId);
|
||||
this.ipcRemoteId = remoteId;
|
||||
} else if(this.ipcRemoteId !== remoteId) {
|
||||
if(this.windowClosedTestInterval > 0) {
|
||||
clearInterval(this.windowClosedTestInterval);
|
||||
this.windowClosedTestInterval = 0;
|
||||
|
||||
log.debug(LogCategory.IPC, tr("Remote window got reconnected. Client reloaded it."));
|
||||
} else {
|
||||
log.warn(LogCategory.IPC, tr("Remote window got a new id. Maybe a reload?"));
|
||||
}
|
||||
this.ipcRemoteId = remoteId;
|
||||
}
|
||||
|
||||
|
|
|
@ -33,18 +33,18 @@ export abstract class EventControllerBase<Type extends "controller" | "popout">
|
|||
protected ipcChannel: IPCChannel;
|
||||
protected ipcRemoteId: string;
|
||||
|
||||
protected readonly localEventRegistry: Registry<string>;
|
||||
private readonly localEventReceiver: EventReceiver<string>;
|
||||
protected readonly localEventRegistry: Registry;
|
||||
private readonly localEventReceiver: EventReceiver;
|
||||
|
||||
private omitEventType: string = undefined;
|
||||
private omitEventData: any;
|
||||
private eventFiredListeners: {[key: string]:{ callback: () => void, timeout: number }} = {};
|
||||
|
||||
protected constructor(localEventRegistry: Registry<string>) {
|
||||
protected constructor(localEventRegistry: Registry) {
|
||||
this.localEventRegistry = localEventRegistry;
|
||||
|
||||
let refThis = this;
|
||||
this.localEventReceiver = new class implements EventReceiver<{}> {
|
||||
this.localEventReceiver = new class implements EventReceiver {
|
||||
fire<T extends keyof {}>(eventType: T, data?: any[T], overrideTypeKey?: boolean) {
|
||||
if(refThis.omitEventType === eventType && refThis.omitEventData === data) {
|
||||
refThis.omitEventType = undefined;
|
||||
|
@ -70,7 +70,7 @@ export abstract class EventControllerBase<Type extends "controller" | "popout">
|
|||
}
|
||||
}
|
||||
};
|
||||
this.localEventRegistry.connectAll(this.localEventReceiver as any);
|
||||
this.localEventRegistry.connectAll(this.localEventReceiver);
|
||||
}
|
||||
|
||||
protected handleIPCMessage(remoteId: string, broadcast: boolean, message: ChannelMessage) {
|
||||
|
@ -112,7 +112,7 @@ export abstract class EventControllerBase<Type extends "controller" | "popout">
|
|||
}
|
||||
|
||||
protected destroyIPC() {
|
||||
this.localEventRegistry.disconnectAll(this.localEventReceiver as any);
|
||||
this.localEventRegistry.disconnectAll(this.localEventReceiver);
|
||||
this.ipcChannel = undefined;
|
||||
this.ipcRemoteId = undefined;
|
||||
this.eventFiredListeners = {};
|
||||
|
|
|
@ -1,6 +1,17 @@
|
|||
import {Registry} from "tc-shared/events";
|
||||
import {ExternalModalController} from "tc-shared/ui/react-elements/external-modal/Controller";
|
||||
import {ModalController} from "tc-shared/ui/react-elements/Modal";
|
||||
import "./Controller"; /* we've to reference him here, else the client would not */
|
||||
|
||||
export function spawnExternalModal<EventClass>(modal: string, events: Registry<EventClass>, userData: any) : ExternalModalController {
|
||||
return new ExternalModalController(modal, events as any, userData);
|
||||
export type ControllerFactory = (modal: string, events: Registry, userData: any) => ModalController;
|
||||
let modalControllerFactory: ControllerFactory;
|
||||
|
||||
export function setExternalModalControllerFactory(factory: ControllerFactory) {
|
||||
modalControllerFactory = factory;
|
||||
}
|
||||
|
||||
export function spawnExternalModal<EventClass extends { [key: string]: any }>(modal: string, events: Registry<EventClass>, userData: any) : ModalController {
|
||||
if(typeof modalControllerFactory === "undefined")
|
||||
throw tr("No external modal factory has been set");
|
||||
|
||||
return modalControllerFactory(modal, events as any, userData);
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import * as log from "tc-shared/log";
|
||||
import {LogCategory} from "tc-shared/log";
|
||||
import {LogCategory, logError} from "tc-shared/log";
|
||||
import {spawnExternalModal} from "tc-shared/ui/react-elements/external-modal";
|
||||
import {EventHandler, Registry} from "tc-shared/events";
|
||||
import {VideoViewerEvents} from "./Definitions";
|
||||
|
@ -66,7 +66,6 @@ class VideoViewer {
|
|||
|
||||
this.events.fire_async("notify_following", { watcherId: undefined });
|
||||
this.events.fire_async("notify_video", { url: url });
|
||||
this.modal.show();
|
||||
}
|
||||
|
||||
async open() {
|
||||
|
@ -313,6 +312,7 @@ let currentVideoViewer: VideoViewer;
|
|||
export function openVideoViewer(connection: ConnectionHandler, url: string) {
|
||||
if(currentVideoViewer?.connection === connection) {
|
||||
currentVideoViewer.setWatchingVideo(url);
|
||||
currentVideoViewer.open(); /* draw focus */
|
||||
return;
|
||||
} else if(currentVideoViewer) {
|
||||
currentVideoViewer.destroy();
|
||||
|
@ -323,9 +323,13 @@ export function openVideoViewer(connection: ConnectionHandler, url: string) {
|
|||
currentVideoViewer.events.on("notify_destroy", () => {
|
||||
currentVideoViewer = undefined;
|
||||
});
|
||||
currentVideoViewer.open();
|
||||
currentVideoViewer.open().catch(error => {
|
||||
logError(LogCategory.GENERAL, tr("Failed to open video viewer: %o"), error);
|
||||
currentVideoViewer.destroy();
|
||||
currentVideoViewer = undefined;
|
||||
});
|
||||
}
|
||||
|
||||
window.onunload = () => {
|
||||
window.onbeforeunload = () => {
|
||||
currentVideoViewer?.destroy();
|
||||
};
|
|
@ -1,19 +1,19 @@
|
|||
interface PlayerStatusPlaying {
|
||||
export interface PlayerStatusPlaying {
|
||||
status: "playing";
|
||||
|
||||
timestampPlay: number;
|
||||
timestampBuffer: number;
|
||||
}
|
||||
|
||||
interface PlayerStatusBuffering {
|
||||
export interface PlayerStatusBuffering {
|
||||
status: "buffering";
|
||||
}
|
||||
|
||||
interface PlayerStatusStopped {
|
||||
export interface PlayerStatusStopped {
|
||||
status: "stopped";
|
||||
}
|
||||
|
||||
interface PlayerStatusPaused {
|
||||
export interface PlayerStatusPaused {
|
||||
status: "paused";
|
||||
}
|
||||
|
||||
|
|
|
@ -91,10 +91,10 @@ const WatcherInfo = React.memo((props: { events: Registry<VideoViewerEvents>, wa
|
|||
|
||||
let renderedAvatar;
|
||||
if(clientInfo === "loading") {
|
||||
renderedAvatar = <AvatarRenderer avatar={"loading"} key={"loading-avatar"} />;
|
||||
renderedAvatar = <AvatarRenderer className={cssStyle.avatar} avatar={"loading"} key={"loading-avatar"} />;
|
||||
} else {
|
||||
const avatar = getGlobalAvatarManagerFactory().getManager(props.handlerId).resolveClientAvatar({ id: clientInfo.clientId, clientUniqueId: clientInfo.uniqueId });
|
||||
renderedAvatar = <AvatarRenderer avatar={avatar} key={"client-avatar"} />;
|
||||
renderedAvatar = <AvatarRenderer className={cssStyle.avatar} avatar={avatar} key={"client-avatar"} />;
|
||||
}
|
||||
|
||||
let renderedClientName;
|
||||
|
@ -154,9 +154,7 @@ const WatcherInfo = React.memo((props: { events: Registry<VideoViewerEvents>, wa
|
|||
}}
|
||||
>
|
||||
<div className={cssStyle.containerAvatar}>
|
||||
<div className={cssStyle.avatar}>
|
||||
{renderedAvatar}
|
||||
</div>
|
||||
{renderedAvatar}
|
||||
</div>
|
||||
<div className={cssStyle.containerDetail}>
|
||||
<a className={cssStyle.username}>
|
||||
|
|
|
@ -40,7 +40,7 @@ function eliminate_imports(node: ts.Node, ctx: ts.TransformationContext, data: I
|
|||
case SyntaxKind.ImportDeclaration:
|
||||
const import_decl = node as ts.ImportDeclaration;
|
||||
const clause = import_decl.importClause;
|
||||
if(!clause.namedBindings) return node;
|
||||
if(!clause?.namedBindings) return node;
|
||||
|
||||
let new_binding;
|
||||
if(clause.namedBindings.kind === SyntaxKind.NamedImports) {
|
||||
|
@ -260,6 +260,9 @@ function analyze_type_node(node: ts.TypeNode | ts.LeftHandSideExpression, data:
|
|||
analyze_type_node(ct.type, data);
|
||||
break;
|
||||
|
||||
case SyntaxKind.TupleType:
|
||||
break;
|
||||
|
||||
default:
|
||||
throw "Unknown type " + SyntaxKind[node.kind] + ". Extend me :)";
|
||||
}
|
||||
|
|
|
@ -0,0 +1,133 @@
|
|||
import {AbstractExternalModalController} from "tc-shared/ui/react-elements/external-modal/Controller";
|
||||
import {spawnYesNo} from "tc-shared/ui/modal/ModalYesNo";
|
||||
import * as ipc from "tc-shared/ipc/BrowserIPC";
|
||||
import * as loader from "tc-loader";
|
||||
import {Stage} from "tc-loader";
|
||||
import {setExternalModalControllerFactory} from "tc-shared/ui/react-elements/external-modal";
|
||||
import {ChannelMessage} from "tc-shared/ipc/BrowserIPC";
|
||||
import {LogCategory, logDebug, logWarn} from "tc-shared/log";
|
||||
|
||||
class ExternalModalController extends AbstractExternalModalController {
|
||||
private currentWindow: Window;
|
||||
private windowClosedTestInterval: number = 0;
|
||||
private windowClosedTimeout: number;
|
||||
|
||||
constructor(a, b, c) {
|
||||
super(a, b, c);
|
||||
}
|
||||
|
||||
protected async spawnWindow() : Promise<boolean> {
|
||||
if(this.currentWindow)
|
||||
return true;
|
||||
|
||||
this.currentWindow = this.trySpawnWindow0();
|
||||
if(!this.currentWindow) {
|
||||
await new Promise((resolve, reject) => {
|
||||
spawnYesNo(tr("Would you like to open the popup?"), tra("Would you like to open popup {}?", this.modalType), callback => {
|
||||
if(!callback) {
|
||||
reject("user aborted");
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentWindow = this.trySpawnWindow0();
|
||||
if(window) {
|
||||
reject(tr("Failed to spawn window"));
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
}).close_listener.push(() => reject(tr("user aborted")));
|
||||
});
|
||||
}
|
||||
|
||||
if(!this.currentWindow)
|
||||
return false;
|
||||
|
||||
this.currentWindow.onbeforeunload = () => {
|
||||
clearInterval(this.windowClosedTestInterval);
|
||||
|
||||
this.windowClosedTimeout = Date.now() + 5000;
|
||||
this.windowClosedTestInterval = setInterval(() => {
|
||||
if(!this.currentWindow) {
|
||||
clearInterval(this.windowClosedTestInterval);
|
||||
this.windowClosedTestInterval = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.currentWindow.closed || Date.now() > this.windowClosedTimeout) {
|
||||
clearInterval(this.windowClosedTestInterval);
|
||||
this.windowClosedTestInterval = 0;
|
||||
this.handleWindowClosed();
|
||||
}
|
||||
}, 100);
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected destroyWindow() {
|
||||
clearInterval(this.windowClosedTestInterval);
|
||||
this.windowClosedTestInterval = 0;
|
||||
|
||||
if(this.currentWindow) {
|
||||
this.currentWindow.close();
|
||||
this.currentWindow = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
protected focusWindow() {
|
||||
this.currentWindow?.focus();
|
||||
}
|
||||
|
||||
private trySpawnWindow0() : Window | null {
|
||||
const parameters = {
|
||||
"loader-target": "manifest",
|
||||
"chunk": "modal-external",
|
||||
"modal-target": this.modalType,
|
||||
"ipc-channel": this.ipcChannel.channelId,
|
||||
"ipc-address": ipc.getInstance().getLocalAddress(),
|
||||
"disableGlobalContextMenu": __build.mode === "debug" ? 1 : 0,
|
||||
"loader-abort": __build.mode === "debug" ? 1 : 0,
|
||||
};
|
||||
|
||||
const features = {
|
||||
status: "no",
|
||||
location: "no",
|
||||
toolbar: "no",
|
||||
menubar: "no",
|
||||
/*
|
||||
width: 600,
|
||||
height: 400
|
||||
*/
|
||||
};
|
||||
|
||||
let baseUrl = location.origin + location.pathname + "?";
|
||||
return window.open(
|
||||
baseUrl + Object.keys(parameters).map(e => e + "=" + encodeURIComponent(parameters[e])).join("&"),
|
||||
this.modalType,
|
||||
Object.keys(features).map(e => e + "=" + features[e]).join(",")
|
||||
);
|
||||
}
|
||||
|
||||
protected handleIPCMessage(remoteId: string, broadcast: boolean, message: ChannelMessage) {
|
||||
if(!broadcast && this.ipcRemoteId !== remoteId) {
|
||||
if(this.windowClosedTestInterval > 0) {
|
||||
clearInterval(this.windowClosedTestInterval);
|
||||
this.windowClosedTestInterval = 0;
|
||||
|
||||
logDebug(LogCategory.IPC, tr("Remote window got reconnected. Client reloaded it."));
|
||||
} else {
|
||||
logWarn(LogCategory.IPC, tr("Remote window got a new id. Maybe a reload?"));
|
||||
}
|
||||
}
|
||||
|
||||
super.handleIPCMessage(remoteId, broadcast, message);
|
||||
}
|
||||
}
|
||||
|
||||
loader.register_task(Stage.JAVASCRIPT_INITIALIZING, {
|
||||
priority: 50,
|
||||
name: "external modal controller factory setup",
|
||||
function: async () => {
|
||||
setExternalModalControllerFactory((modal, events, userData) => new ExternalModalController(modal, events, userData));
|
||||
}
|
||||
});
|
|
@ -1,5 +1,6 @@
|
|||
import "webrtc-adapter";
|
||||
import "./index.scss";
|
||||
import "./FileTransfer";
|
||||
import "./ExternalModalFactory";
|
||||
|
||||
export = require("tc-shared/main");
|
Loading…
Reference in New Issue