@difizen/mana-core
Version:
272 lines (243 loc) • 8.56 kB
text/typescript
/* eslint-disable @typescript-eslint/no-invalid-this */
import type { MaybePromise, Event, Newable, Disposable } from '@difizen/mana-common';
import { DisposableCollection } from '@difizen/mana-common';
import { Priority, Emitter } from '@difizen/mana-common';
import { prop } from '@difizen/mana-observable';
import type { Contribution } from '@difizen/mana-syringe';
import { contrib, inject, singleton } from '@difizen/mana-syringe';
import { DebugService } from '../common/debug';
import type { ManaContext } from '../module';
import { ViewMeta } from './view-meta';
import type { ViewPreference, View } from './view-protocol';
import type { SlotView } from './view-protocol';
import { ViewDefineToken } from './view-protocol';
import { ViewFactory, ViewPreferenceContribution } from './view-protocol';
export interface ViewCreateOptions {
factoryId: string;
options?: any;
}
export interface ViewEvent {
readonly view: View;
readonly factoryId: string;
}
/**
* 管理视图
* 为视图保存创建条件
*/
export class ViewManager implements Disposable {
root?: View;
protected factoryMap?: Map<string, ViewFactory> | undefined;
protected factories: string[] = [];
protected preferenceMap?: Map<string, ViewPreference> | undefined;
protected preferences: string[] = [];
protected readonly viewContextMap = new Map<View, ManaContext>();
protected readonly viewFactoryMap = new Map<View, string>();
protected readonly views = new Map<string, View>();
protected readonly viewPromises = new Map<string, MaybePromise<View>>();
protected readonly pendingViewPromises = new Map<string, MaybePromise<View>>();
protected readonly onWillCreateViewEmitter = new Emitter<ViewEvent>();
readonly onWillCreateView: Event<ViewEvent> = this.onWillCreateViewEmitter.event;
protected readonly onDidCreateViewEmitter = new Emitter<ViewEvent>();
readonly onDidCreateView: Event<ViewEvent> = this.onDidCreateViewEmitter.event;
readonly toDispose = new DisposableCollection();
protected readonly factoryProvider: Contribution.Provider<ViewFactory>;
protected readonly debugService: DebugService;
protected readonly preferenceProvider: Contribution.Provider<ViewPreferenceContribution>;
disposed?: boolean | undefined;
constructor(
factoryProvider: Contribution.Provider<ViewFactory>,
debugService: DebugService,
preferenceProvider: Contribution.Provider<ViewPreferenceContribution>,
) {
this.factoryProvider = factoryProvider;
this.debugService = debugService;
this.preferenceProvider = preferenceProvider;
this.toDispose.push(this.onDidCreateViewEmitter);
this.toDispose.push(this.onWillCreateViewEmitter);
this.toDispose.push(
this.factoryProvider.onChanged(() => {
this.factoryMap = undefined;
}),
);
}
dispose() {
if (this.disposed) {
return;
}
this.disposed = true;
this.viewPromises.clear();
this.pendingViewPromises.clear();
}
getFactoryMap(): Map<string, ViewFactory> {
if (!this.factoryMap) {
this.factoryMap = new Map();
for (const factory of this.factoryProvider.getContributions()) {
if (factory.id) {
this.factoryMap.set(factory.id, factory);
}
}
this.factories = [];
for (const id of this.factoryMap.keys()) {
this.factories.push(id);
}
}
return this.factoryMap;
}
toFactoryId(view: Newable<View> | string): string {
if (typeof view === 'string') {
return view;
}
const factoryId = Reflect.getMetadata(ViewDefineToken, view);
return factoryId;
}
getPreferenceMap(): Map<string, ViewPreference> {
if (!this.preferenceMap) {
this.preferenceMap = new Map();
for (const viewPreference of this.preferenceProvider.getContributions()) {
viewPreference.forEach((item) => {
const { view } = item;
const factoryId = this.toFactoryId(view);
const currentPriority = item.priority ?? Priority.IDLE;
const last = this.preferenceMap?.get(factoryId);
if (!last || !last.priority || currentPriority > last.priority) {
this.preferenceMap?.set(factoryId, item);
}
});
}
this.preferences = [];
for (const id of this.preferenceMap.keys()) {
this.preferences.push(id);
}
}
return this.preferenceMap;
}
toKey(options: ViewCreateOptions): string {
return JSON.stringify(options);
}
fromKey(key: string): ViewCreateOptions {
return JSON.parse(key);
}
getView<T extends View>(
factoryOrView: string | Newable<View>,
options?: any,
): MaybePromise<T> | undefined {
const factoryId = this.toFactoryId(factoryOrView);
const key = this.toKey({ factoryId, options });
const pendingView = this.doGetView<T>(key);
return pendingView;
}
getViewByKey(key: string) {
const pending = this.pendingViewPromises.get(key);
if (pending) {
return pending;
}
return this.views.get(key);
}
async getViews<T extends View>(factoryId: string): Promise<T[]> {
const views: T[] = [];
for (const [key] of this.views.entries()) {
if (this.fromKey(key).factoryId === factoryId) {
const pendingView = this.doGetView<T>(key);
const view = pendingView && (await pendingView);
if (view) {
views.push(view);
}
}
}
return views;
}
protected doGetView<T extends View>(key: string): MaybePromise<T> | undefined {
const pendingView = this.viewPromises.get(key) ?? this.pendingViewPromises.get(key);
if (pendingView) {
return pendingView as MaybePromise<T>;
}
return undefined;
}
getFactory(view: Newable<View> | Newable<SlotView>): ViewFactory | undefined {
const factoryId = this.toFactoryId(view);
const factories = this.getFactoryMap();
try {
return factories.get(factoryId);
} catch (ex) {
console.error(ex);
return;
}
}
getPrefer(factoryId: string): ViewPreference | undefined {
return this.getPreferenceMap().get(factoryId);
}
getFactoryIdByView(view: View): string | undefined {
return this.viewFactoryMap.get(view);
}
getViewOption(view: View) {
for (const [key, value] of this.views.entries()) {
if (value === view) {
return this.fromKey(key).options;
} else {
continue;
}
}
}
setViewContext(view: View, context: ManaContext) {
return ViewMeta.setViewContext(view, context);
}
getViewContext(view: View) {
return ViewMeta.getViewContext(view) as ManaContext;
}
removeViewContext(view: View) {
return ViewMeta.removeViewContext(view);
}
getViewKey(view: View) {
for (const [key, value] of this.views.entries()) {
if (value === view) {
return key;
}
}
return undefined;
}
protected mergeViewOption(options?: Record<any, any>, preference?: ViewPreference) {
if (!options && !preference?.options) {
return undefined;
}
return { ...preference?.options, ...options };
}
async getOrCreateView<T extends View, U extends Record<any, any> = any>(
factoryOrView: string | Newable<T>,
options?: U,
): Promise<T> {
const factoryId = this.toFactoryId(factoryOrView);
const key = this.toKey({ factoryId, options });
const existingView = this.doGetView<T>(key);
if (existingView) {
return existingView;
}
const factory = this.getFactoryMap().get(factoryId);
if (!factory) {
throw Error(`No View factory '${factoryId}' has been registered.`);
}
try {
const preference = this.getPreferenceMap().get(factoryId);
const viewOption = this.mergeViewOption(options, preference);
const viewPromise = factory.createView(viewOption);
this.pendingViewPromises.set(key, viewPromise);
const view = await viewPromise;
this.onWillCreateViewEmitter.fire({ factoryId, view });
this.viewPromises.set(key, viewPromise);
this.views.set(key, view);
this.viewFactoryMap.set(view, factoryId);
view.onDisposed(() => {
this.views.delete(key);
this.viewPromises.delete(key);
this.viewFactoryMap.delete(view);
this.removeViewContext(view);
});
this.onDidCreateViewEmitter.fire({ factoryId, view });
return view as T;
} finally {
this.pendingViewPromises.delete(key);
}
}
}