2022-01-27 14:28:01 +01:00
|
|
|
import { Component, ComponentConstructor } from "../component/component";
|
2021-11-13 09:02:34 +01:00
|
|
|
import { ComponentNode } from "../component/component_node";
|
|
|
|
|
import { MountOptions } from "../component/fibers";
|
|
|
|
|
import { Scheduler } from "../component/scheduler";
|
2021-12-20 17:32:32 +01:00
|
|
|
import { TemplateSet, TemplateSetConfig } from "./template_set";
|
2021-12-01 15:42:22 +01:00
|
|
|
import { nodeErrorHandlers } from "../component/error_handling";
|
2022-01-31 16:57:58 +01:00
|
|
|
import { validateTarget } from "../utils";
|
2020-11-26 16:45:25 +01:00
|
|
|
|
|
|
|
|
// reimplement dev mode stuff see last change in 0f7a8289a6fb8387c3c1af41c6664b2a8448758f
|
|
|
|
|
|
2021-11-15 13:10:09 +01:00
|
|
|
export interface Env {
|
|
|
|
|
[key: string]: any;
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-27 14:28:01 +01:00
|
|
|
export interface AppConfig<P, E> extends TemplateSetConfig {
|
|
|
|
|
props?: P;
|
|
|
|
|
env?: E;
|
2022-01-28 13:39:03 +01:00
|
|
|
test?: boolean;
|
2021-10-20 16:08:31 +02:00
|
|
|
}
|
|
|
|
|
|
2022-02-15 10:14:50 +01:00
|
|
|
export const DEV_MSG = () => {
|
|
|
|
|
const hash = (window as any).owl ? (window as any).owl.__info__.hash : "master";
|
|
|
|
|
|
|
|
|
|
return `Owl is running in 'dev' mode.
|
2021-10-20 16:08:31 +02:00
|
|
|
|
|
|
|
|
This is not suitable for production use.
|
2022-02-15 10:14:50 +01:00
|
|
|
See https://github.com/odoo/owl/blob/${hash}/doc/reference/app.md#configuration for more information.`;
|
|
|
|
|
};
|
2021-10-20 16:08:31 +02:00
|
|
|
|
2022-01-27 14:28:01 +01:00
|
|
|
export class App<
|
|
|
|
|
T extends abstract new (...args: any) => any = any,
|
2022-01-10 14:35:57 +01:00
|
|
|
P extends object = any,
|
2022-01-27 14:28:01 +01:00
|
|
|
E = any
|
|
|
|
|
> extends TemplateSet {
|
2022-02-01 16:08:01 +01:00
|
|
|
static validateTarget = validateTarget;
|
|
|
|
|
|
2022-01-27 14:28:01 +01:00
|
|
|
Root: ComponentConstructor<P, E>;
|
|
|
|
|
props: P;
|
|
|
|
|
env: E;
|
2022-01-20 10:13:17 +01:00
|
|
|
scheduler = new Scheduler();
|
2022-01-27 14:28:01 +01:00
|
|
|
root: ComponentNode<P, E> | null = null;
|
2020-11-26 16:45:25 +01:00
|
|
|
|
2022-01-27 14:28:01 +01:00
|
|
|
constructor(Root: ComponentConstructor<P, E>, config: AppConfig<P, E> = {}) {
|
2021-12-20 17:32:32 +01:00
|
|
|
super(config);
|
2020-11-26 16:45:25 +01:00
|
|
|
this.Root = Root;
|
2022-01-28 13:39:03 +01:00
|
|
|
if (config.test) {
|
|
|
|
|
this.dev = true;
|
|
|
|
|
}
|
|
|
|
|
if (this.dev && !config.test) {
|
2022-02-15 10:14:50 +01:00
|
|
|
console.info(DEV_MSG());
|
2021-10-20 16:08:31 +02:00
|
|
|
}
|
2021-12-20 17:32:32 +01:00
|
|
|
const descrs = Object.getOwnPropertyDescriptors(config.env || {});
|
2022-01-27 14:28:01 +01:00
|
|
|
this.env = Object.freeze(Object.defineProperties({}, descrs)) as E;
|
|
|
|
|
this.props = config.props || ({} as P);
|
2020-11-26 16:45:25 +01:00
|
|
|
}
|
|
|
|
|
|
2022-01-27 14:28:01 +01:00
|
|
|
mount(target: HTMLElement, options?: MountOptions): Promise<Component<P, E> & InstanceType<T>> {
|
2022-02-01 16:08:01 +01:00
|
|
|
App.validateTarget(target);
|
2021-12-08 10:07:27 +01:00
|
|
|
const node = this.makeNode(this.Root, this.props);
|
|
|
|
|
const prom = this.mountNode(node, target, options);
|
|
|
|
|
this.root = node;
|
|
|
|
|
return prom;
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-27 14:28:01 +01:00
|
|
|
makeNode(Component: ComponentConstructor, props: any): ComponentNode {
|
2021-12-08 10:07:27 +01:00
|
|
|
return new ComponentNode(Component, props, this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mountNode(node: ComponentNode, target: HTMLElement, options?: MountOptions) {
|
2021-11-30 22:21:27 +01:00
|
|
|
const promise: any = new Promise((resolve, reject) => {
|
2021-12-15 15:55:13 +01:00
|
|
|
let isResolved = false;
|
2021-12-08 10:07:27 +01:00
|
|
|
// manually set a onMounted callback.
|
|
|
|
|
// that way, we are independant from the current node.
|
|
|
|
|
node.mounted.push(() => {
|
|
|
|
|
resolve(node.component);
|
2021-12-15 15:55:13 +01:00
|
|
|
isResolved = true;
|
2021-12-08 10:07:27 +01:00
|
|
|
});
|
2021-12-01 15:42:22 +01:00
|
|
|
|
|
|
|
|
// Manually add the last resort error handler on the node
|
|
|
|
|
let handlers = nodeErrorHandlers.get(node);
|
|
|
|
|
if (!handlers) {
|
|
|
|
|
handlers = [];
|
|
|
|
|
nodeErrorHandlers.set(node, handlers);
|
|
|
|
|
}
|
|
|
|
|
handlers.unshift((e) => {
|
2021-12-15 15:55:13 +01:00
|
|
|
if (isResolved) {
|
|
|
|
|
console.error(e);
|
|
|
|
|
} else {
|
|
|
|
|
reject(e);
|
|
|
|
|
}
|
2021-11-30 22:21:27 +01:00
|
|
|
throw e;
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
node.mountComponent(target, options);
|
|
|
|
|
return promise;
|
2020-11-26 16:45:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
destroy() {
|
|
|
|
|
if (this.root) {
|
|
|
|
|
this.root.destroy();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-12-20 17:32:32 +01:00
|
|
|
|
2022-01-10 14:35:57 +01:00
|
|
|
export async function mount<
|
|
|
|
|
T extends abstract new (...args: any) => any = any,
|
|
|
|
|
P extends object = any,
|
|
|
|
|
E = any
|
|
|
|
|
>(
|
2022-01-27 14:28:01 +01:00
|
|
|
C: T & ComponentConstructor<P, E>,
|
2021-12-20 17:32:32 +01:00
|
|
|
target: HTMLElement,
|
2022-01-27 14:28:01 +01:00
|
|
|
config: AppConfig<P, E> & MountOptions = {}
|
|
|
|
|
): Promise<Component<P, E> & InstanceType<T>> {
|
2021-12-20 17:32:32 +01:00
|
|
|
return new App(C, config).mount(target, config);
|
|
|
|
|
}
|