client-ts: Port services/app/app_context

This commit is contained in:
Elian Doran 2024-07-25 20:55:04 +03:00
parent cd5ea28e14
commit b7754bcea0
No known key found for this signature in database
5 changed files with 48 additions and 21 deletions

View File

@ -14,8 +14,32 @@ import MainTreeExecutors from "./main_tree_executors.js";
import toast from "../services/toast.js"; import toast from "../services/toast.js";
import ShortcutComponent from "./shortcut_component.js"; import ShortcutComponent from "./shortcut_component.js";
interface Layout {
getRootWidget: (appContext: AppContext) => RootWidget;
}
interface RootWidget extends Component {
render: () => JQuery<HTMLElement>;
}
interface BeforeUploadListener extends Component {
beforeUnloadEvent(): boolean;
}
interface TriggerData {
noteIds?: string[];
callback?: () => void;
}
class AppContext extends Component { class AppContext extends Component {
constructor(isMainWindow) {
isMainWindow: boolean;
components: Component[];
beforeUnloadListeners: WeakRef<BeforeUploadListener>[];
tabManager!: TabManager;
layout?: Layout;
constructor(isMainWindow: boolean) {
super(); super();
this.isMainWindow = isMainWindow; this.isMainWindow = isMainWindow;
@ -24,7 +48,7 @@ class AppContext extends Component {
this.beforeUnloadListeners = []; this.beforeUnloadListeners = [];
} }
setLayout(layout) { setLayout(layout: Layout) {
this.layout = layout; this.layout = layout;
} }
@ -68,6 +92,10 @@ class AppContext extends Component {
} }
renderWidgets() { renderWidgets() {
if (!this.layout) {
throw new Error("Missing layout.");
}
const rootWidget = this.layout.getRootWidget(this); const rootWidget = this.layout.getRootWidget(this);
const $renderedWidget = rootWidget.render(); const $renderedWidget = rootWidget.render();
@ -92,15 +120,13 @@ class AppContext extends Component {
this.triggerEvent('initialRenderComplete'); this.triggerEvent('initialRenderComplete');
} }
/** @returns {Promise<void>} */ triggerEvent(name: string, data: TriggerData = {}) {
triggerEvent(name, data = {}) {
return this.handleEvent(name, data); return this.handleEvent(name, data);
} }
/** @returns {Promise<*>} */ triggerCommand(name: string, data: TriggerData = {}) {
triggerCommand(name, data = {}) {
for (const executor of this.components) { for (const executor of this.components) {
const fun = executor[`${name}Command`]; const fun = (executor as any)[`${name}Command`];
if (fun) { if (fun) {
return executor.callMethod(fun, data); return executor.callMethod(fun, data);
@ -114,17 +140,17 @@ class AppContext extends Component {
return this.triggerEvent(name, data); return this.triggerEvent(name, data);
} }
getComponentByEl(el) { getComponentByEl(el: HTMLElement) {
return $(el).closest(".component").prop('component'); return $(el).closest(".component").prop('component');
} }
addBeforeUnloadListener(obj) { addBeforeUnloadListener(obj: BeforeUploadListener) {
if (typeof WeakRef !== "function") { if (typeof WeakRef !== "function") {
// older browsers don't support WeakRef // older browsers don't support WeakRef
return; return;
} }
this.beforeUnloadListeners.push(new WeakRef(obj)); this.beforeUnloadListeners.push(new WeakRef<BeforeUploadListener>(obj));
} }
} }

View File

@ -12,10 +12,10 @@ import utils from '../services/utils.js';
* event / command is executed in all components - by simply awaiting the `triggerEvent()`. * event / command is executed in all components - by simply awaiting the `triggerEvent()`.
*/ */
export default class Component { export default class Component {
private componentId: string; componentId: string;
private children: Component[]; children: Component[];
private initialized: Promise<void> | null; initialized: Promise<void> | null;
private parent?: Component; parent?: Component;
constructor() { constructor() {
this.componentId = `${this.sanitizedClassName}-${utils.randomString(8)}`; this.componentId = `${this.sanitizedClassName}-${utils.randomString(8)}`;
@ -64,7 +64,7 @@ export default class Component {
} }
} }
triggerEvent(name: string, data = {}): Promise<unknown> | undefined { triggerEvent(name: string, data = {}): Promise<unknown> | undefined | null {
return this.parent?.triggerEvent(name, data); return this.parent?.triggerEvent(name, data);
} }
@ -83,7 +83,7 @@ export default class Component {
return promises.length > 0 ? Promise.all(promises) : null; return promises.length > 0 ? Promise.all(promises) : null;
} }
triggerCommand(name: string, data = {}): Promise<unknown> | undefined { triggerCommand(name: string, data = {}): Promise<unknown> | undefined | null {
const fun = (this as any)[`${name}Command`]; const fun = (this as any)[`${name}Command`];
if (fun) { if (fun) {

View File

@ -31,7 +31,7 @@ interface SearchNoteResponse {
* Backend has a similar cache called Becca * Backend has a similar cache called Becca
*/ */
class FrocaImpl implements Froca { class FrocaImpl implements Froca {
private initializedPromise: Promise<void>; initializedPromise: Promise<void>;
notes!: Record<string, FNote>; notes!: Record<string, FNote>;
branches!: Record<string, FBranch>; branches!: Record<string, FBranch>;

View File

@ -100,10 +100,10 @@ function isCtrlKey(evt: KeyboardEvent) {
|| (isMac() && evt.metaKey); || (isMac() && evt.metaKey);
} }
function assertArguments() { function assertArguments(...args: string[]) {
for (const i in arguments) { for (const i in args) {
if (!arguments[i]) { if (!args[i]) {
console.trace(`Argument idx#${i} should not be falsy: ${arguments[i]}`); console.trace(`Argument idx#${i} should not be falsy: ${args[i]}`);
} }
} }
} }

View File

@ -26,6 +26,7 @@ interface CustomGlobals {
baseApiUrl: string; baseApiUrl: string;
isProtectedSessionAvailable: boolean; isProtectedSessionAvailable: boolean;
isDev: boolean; isDev: boolean;
isMainWindow: boolean;
maxEntityChangeIdAtLoad: number; maxEntityChangeIdAtLoad: number;
maxEntityChangeSyncIdAtLoad: number; maxEntityChangeSyncIdAtLoad: number;
} }