@paperbits/core
Version:
Paperbits core components.
178 lines (141 loc) • 6.69 kB
text/typescript
import * as ko from "knockout";
import template from "./pageHost.html";
import { Contract } from "@paperbits/common";
import { EventManager } from "@paperbits/common/events";
import { Component, OnDestroyed, OnMounted, Param } from "@paperbits/common/ko/decorators";
import { ILayoutService } from "@paperbits/common/layouts";
import { IPageService } from "@paperbits/common/pages";
import { Route, Router } from "@paperbits/common/routing";
import { StyleCompiler, StyleManager } from "@paperbits/common/styles";
import { ViewManager, ViewManagerMode } from "@paperbits/common/ui";
import { ContentViewModel, ContentViewModelBinder } from "../../../content/ko";
import { PopupHostViewModelBinder } from "../../../popup/ko/popupHostViewModelBinder";
import { PopupHost } from "../../../popup/ko/popupHost";
export class PageHost {
private readonly styleManager: StyleManager;
public readonly contentViewModel: ko.Observable<ContentViewModel>;
public readonly popupHostViewModel: ko.Observable<PopupHost>;
constructor(
private readonly contentViewModelBinder: ContentViewModelBinder,
private readonly popupHostViewModelBinder: PopupHostViewModelBinder,
private readonly router: Router,
private readonly eventManager: EventManager,
private readonly viewManager: ViewManager,
private readonly layoutService: ILayoutService,
private readonly pageService: IPageService,
private readonly styleCompiler: StyleCompiler
) {
this.contentViewModel = ko.observable();
this.popupHostViewModel = ko.observable();
this.pageKey = ko.observable();
this.styleManager = new StyleManager(this.eventManager);
}
public pageKey: ko.Observable<string>;
public async initialize(): Promise<void> {
await this.refreshContent();
this.router.addRouteChangeListener(this.onRouteChange);
this.eventManager.addEventListener("onDataPush", () => this.onDataPush());
this.eventManager.addEventListener("onLocaleChange", () => this.onLocaleUpdate());
this.eventManager.addEventListener("onPopupUpdate", () => this.onPopupUpdate());
}
/**
* This event occurs when data gets pushed to the storage. For example, "Undo" command restores the previous state.
*/
private async onDataPush(): Promise<void> {
if (this.viewManager.mode === ViewManagerMode.selecting || this.viewManager.mode === ViewManagerMode.selected) {
await this.refreshContent();
}
}
private async onLocaleUpdate(): Promise<void> {
await this.refreshContent();
}
private async onPopupUpdate(): Promise<void> {
await this.refreshPopupContent();
}
private async refreshContent(): Promise<void> {
this.viewManager.setShutter();
const route = this.router.getCurrentRoute();
let pageContract = await this.pageService.getPageByPermalink(route.path);
if (!pageContract) {
pageContract = await this.pageService.getPageByPermalink("/404");
if (!pageContract) {
this.viewManager.removeShutter();
return;
}
}
const pageContentContract = await this.pageService.getPageContent(pageContract.key);
this.pageKey(pageContract.key);
const styleSheet = await this.styleCompiler.getStyleSheet();
this.styleManager.setStyleSheet(styleSheet);
const activeLayer = "page";
this.viewManager.setActiveLayer(activeLayer);
const pageBindingContext = {
contentItemKey: pageContract.key,
styleManager: this.styleManager,
navigationPath: route.path,
activeLayer: activeLayer,
template: {
page: {
value: pageContentContract,
onValueUpdate: async (updatedContentContract: Contract, changeDescription: string) => {
await this.pageService.updatePageContent(pageContract.key, updatedContentContract, null, changeDescription);
}
}
}
};
const layoutContract = await this.layoutService.getLayoutByPermalink(route.path);
if (!layoutContract) {
throw new Error(`No matching layouts found for page with permalink "${route.path}".`);
}
const layoutContentContract = await this.layoutService.getLayoutContent(layoutContract.key);
const layoutContentViewModel = await this.contentViewModelBinder.contractToViewModel(layoutContentContract, pageBindingContext);
layoutContentViewModel["widgetBinding"].provides = ["html", "js", "interaction"];
this.contentViewModel(layoutContentViewModel);
await this.refreshPopupContent();
this.viewManager.removeShutter();
this.jumpToAnchor(route);
}
private async refreshPopupContent(): Promise<void> {
const route = this.router.getCurrentRoute();
const popupBindingContext = {
styleManager: this.styleManager,
navigationPath: route.path,
contentType: "popup",
layer: "page",
getHostedDocument: () => {
return this.viewManager.getHostDocument();
}
};
const popupHostViewModel = await this.popupHostViewModelBinder.contractToViewModel(popupBindingContext);
popupHostViewModel["widgetBinding"].provides = ["html", "js", "interaction"];
this.popupHostViewModel(popupHostViewModel);
}
private async onRouteChange(route: Route): Promise<void> {
if (route.previous && route.previous.path === route.path) {
return;
}
await this.refreshContent();
}
private jumpToAnchor(route: Route): void {
const hostDocument = this.viewManager.getHostDocument();
if (!route.hash) {
hostDocument.defaultView.scrollTo({ top: 0 });
return;
}
const anchorElementSelector = `[id="${route.hash}"]`; // Hash may not be proper "id" selector.
const anchorElement = hostDocument.querySelector(anchorElementSelector);
if (anchorElement) {
anchorElement.scrollIntoView();
}
}
public dispose(): void {
this.router.removeRouteChangeListener(this.onRouteChange);
}
}