@difizen/mana-core
Version:
200 lines (174 loc) • 6.37 kB
text/typescript
import type { Newable } from '@difizen/mana-common';
import { Emitter } from '@difizen/mana-common';
import { Deferred } from '@difizen/mana-common';
import { Disposable, Priority } from '@difizen/mana-common';
import { prop } from '@difizen/mana-observable';
import { contrib, inject, singleton } from '@difizen/mana-syringe';
import type { Contribution } from '@difizen/mana-syringe';
import { DebugService } from '../common/debug';
import { DefaultSlotView } from './default-slot-view';
import { ViewManager } from './view-manager';
import type { View } from './view-protocol';
import type { SlotPreference, ViewOpenOption } from './view-protocol';
import { SlotPreferenceContribution, SlotView } from './view-protocol';
interface ViewAddArgs {
view?: View;
slot: string;
option?: ViewOpenOption;
}
interface SlotSetArgs {
view?: View;
slot: string;
}
export class SlotViewManager {
protected preferences: Map<string, SlotPreference> = new Map();
protected componentPreferences: Map<string, SlotPreference> = new Map();
protected slotRenderingDeferred: Map<string, Deferred<void>> = new Map();
protected onViewAddedEmitter: Emitter<ViewAddArgs> = new Emitter<ViewAddArgs>();
protected onSlotChangedEmitter: Emitter<SlotSetArgs> = new Emitter<SlotSetArgs>();
get onViewAdded() {
return this.onViewAddedEmitter.event;
}
get onSlotChanged() {
return this.onSlotChangedEmitter.event;
}
/**
* slot -> slotview
*/
slotViewMap: Map<string, View> = new Map<string, View>();
/**
* view -> child slotview
*/
slotChildrenMap: Map<View, string[]> = new Map<View, string[]>();
protected readonly preferenceProvider: Contribution.Provider<SlotPreferenceContribution>;
protected readonly debugService: DebugService;
protected readonly viewManager: ViewManager;
constructor(
preferenceProvider: Contribution.Provider<SlotPreferenceContribution>,
debugService: DebugService,
viewManager: ViewManager,
) {
this.preferenceProvider = preferenceProvider;
this.debugService = debugService;
this.viewManager = viewManager;
this.setPreferencesFromContribution();
}
protected setPreferencesFromContribution(): void {
for (const preference of this.preferenceProvider.getContributions()) {
preference.forEach((item) => {
const { slot } = item;
this.setSlotPreference(slot, item);
});
}
}
protected getPreferences(): Map<string, SlotPreference> {
return this.preferences;
}
setSlotPreference(slot: string, preference: SlotPreference): void {
const last = this.preferences.get(slot);
const currentPriority = preference.priority ?? Priority.IDLE;
if (!last || !last.priority || currentPriority > last.priority) {
this.preferences.set(slot, preference);
}
}
setComponentSlotPreference(slot: string, preference: SlotPreference): void {
const last = this.componentPreferences.get(slot);
const currentPriority = preference.priority ?? Priority.IDLE;
if (!last || !last.priority || currentPriority > last.priority) {
this.componentPreferences.set(slot, preference);
}
}
protected waitSlotRendering(slot: string): Promise<void> {
let deferred = this.slotRenderingDeferred.get(slot);
if (!deferred) {
deferred = new Deferred<void>();
this.slotRenderingDeferred.set(slot, deferred);
}
return deferred.promise;
}
slotRendering(slot: string): void {
let deferred = this.slotRenderingDeferred.get(slot);
if (!deferred) {
deferred = new Deferred<void>();
this.slotRenderingDeferred.set(slot, deferred);
}
deferred.resolve();
}
async getOrCreateSlotView(
slot: string,
defaultSlotView: Newable<View> = DefaultSlotView,
): Promise<SlotView | View> {
const existSlotView = this.slotViewMap.get(slot);
if (existSlotView) {
return existSlotView;
}
let preference = this.preferences.get(slot);
if (!preference) {
await this.waitSlotRendering(slot);
preference = this.componentPreferences.get(slot);
}
const defaultSlotViewFactory = this.viewManager.getFactory(DefaultSlotView)!;
const toFactory = preference?.view ?? defaultSlotView ?? DefaultSlotView;
const slotViewFactory =
this.viewManager.getFactory(toFactory) ?? defaultSlotViewFactory;
const slotOption = { ...preference?.options, area: slot };
const slotView = await this.viewManager.getOrCreateView(
slotViewFactory.id,
slotOption,
);
slotView.onDisposed(this.setSlotView(slot, slotView).dispose);
this.debugService('slot view set', slot, slotView);
return slotView as SlotView;
}
getSlotView(slot: string): View | undefined {
return this.slotViewMap.get(slot);
}
setSlotView(slot: string, view: View): Disposable {
this.slotViewMap.set(slot, view);
this.onSlotChangedEmitter.fire({ view, slot });
return Disposable.create(() => {
if (this.slotViewMap.get(slot) === view) {
this.slotViewMap.delete(slot);
this.onSlotChangedEmitter.fire({ view: undefined, slot });
}
});
}
async addView(view: View, slot: string, option?: ViewOpenOption): Promise<void> {
const slotView = await this.getOrCreateSlotView(slot);
if (SlotView.is(slotView)) {
await slotView.addView(view, option);
this.onViewAddedEmitter.fire({ view, slot, option });
}
}
async removeView(view: View, slot: string): Promise<void> {
const slotView = await this.getOrCreateSlotView(slot);
if (SlotView.is(slotView)) {
await slotView.removeView(view);
}
}
hasSlot(slot: string): boolean {
return this.slotViewMap.has(slot);
}
getSlotChildren(view: View): string[] | undefined {
return this.slotChildrenMap.get(view);
}
addSlotToView(slot: string, slotView: SlotView): void {
this.slotChildrenMap.set(slotView, [
...(this.slotChildrenMap.get(slotView) ?? []),
slot,
]);
}
removeSlotFromView(slot: string, slotView: SlotView): void {
const children = this.slotChildrenMap.get(slotView);
if (children) {
this.slotChildrenMap.set(
slotView,
children.filter((child) => child !== slot),
);
}
}
}