Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

spike: add support for alternative template renderer #1714

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"scripts": {
"ng": "ng",
"start": "yarn precompile && yarn workflow populate_src_assets && concurrently --kill-others --raw \"yarn data-models:serve\" \"yarn data:serve\" \"ng serve --open\"",
"start:local": "yarn workflow populate_src_assets && concurrently --kill-others --raw \"ng serve --open\" \"yarn workflow sync_local\"",
"start:local": "yarn precompile && yarn workflow populate_src_assets && concurrently --kill-others --raw \"ng serve --open\" \"yarn workflow sync_local\"",
"precompile": "yarn scripts compile types",
"build": "yarn precompile && yarn workflow populate_src_assets && ng build --configuration=production",
"data:serve": "yarn workspace app-data serve",
Expand Down
2 changes: 2 additions & 0 deletions packages/data-models/flowTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,8 @@ export namespace FlowTypes {
flow_type: "template";
rows: TemplateRow[];
comments?: string;
/** Optional display type override to render template in experimental format */
template_type?: "default" | "dynamic";
}

export type TemplateRowType =
Expand Down
3 changes: 2 additions & 1 deletion packages/scripts/src/commands/app-data/copy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,8 @@ export const ASSETS_CONTENTS_LIST = ${JSON.stringify(cleanedContents, null, 2)}

private extractContentsData(flow: FlowTypes.FlowTypeWithData): FlowTypes.FlowTypeBase {
// remove rows property (if exists)
const { rows, status, _processed, ...keptFields } = flow;
const { rows, status, _processed, ...templateFields } = flow;
const { template_type, ...keptFields } = templateFields as FlowTypes.Template;
return keptFields as FlowTypes.FlowTypeBase;
}
private sheetsWriteContents(baseFolder: string, contents: ISheetContents) {
Expand Down
12 changes: 6 additions & 6 deletions src/app/feature/template/template.page.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<ion-content>
<plh-template-container
*ngIf="templateName"
[templatename]="templateName"
></plh-template-container>
<div *ngIf="!templateName" class="ion-padding">
<!-- Rendered template -->
<ng-template templateHost></ng-template>

<!-- Template select -->
<ng-template [ngIf]="!templateName ">
<h3>Select a Template</h3>
<ion-searchbar [(ngModel)]="filterTerm" (ionChange)="search()"></ion-searchbar>
<ion-list>
Expand All @@ -13,5 +13,5 @@ <h3>Select a Template</h3>
>{{template.flow_name}}</ion-item
>
</ion-list>
</div>
</ng-template>
</ion-content>
29 changes: 22 additions & 7 deletions src/app/feature/template/template.page.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Component, OnInit } from "@angular/core";
import { Component, OnInit, ViewChild } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { FlowTypes } from "packages/data-models";
import { TemplateHostDirective } from "src/app/shared/components/template/directives/templateHost.directive";
import { TemplateService } from "src/app/shared/components/template/services/template.service";
import { AppDataService } from "src/app/shared/services/data/app-data.service";

@Component({
Expand All @@ -13,14 +15,27 @@ export class TemplatePage implements OnInit {
filterTerm: string;
allTemplates: FlowTypes.FlowTypeBase[] = [];
filteredTemplates: FlowTypes.FlowTypeBase[] = [];
constructor(private route: ActivatedRoute, private appDataService: AppDataService) {}

ngOnInit() {
this.templateName = this.route.snapshot.params.templateName;
const allTemplates = this.appDataService.listSheetsByType("template");
@ViewChild(TemplateHostDirective, { static: true }) templateHost!: TemplateHostDirective;

constructor(
private route: ActivatedRoute,
private appDataService: AppDataService,
private templateService: TemplateService
) {}

this.allTemplates = allTemplates.sort((a, b) => (a.flow_name > b.flow_name ? 1 : -1));
this.filteredTemplates = allTemplates;
ngOnInit() {
const templateName = this.route.snapshot.params.templateName;
if (templateName) {
this.templateName = templateName;
this.templateService.injectTemplate(templateName, this.templateHost);
}
// Display list of all templates if not specified
else {
const allTemplates = this.appDataService.listSheetsByType("template");
this.allTemplates = allTemplates.sort((a, b) => (a.flow_name > b.flow_name ? 1 : -1));
this.filteredTemplates = allTemplates;
}
}

search() {
Expand Down
8 changes: 8 additions & 0 deletions src/app/shared/components/template/containers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { TemplateContainerComponent } from "../template-container.component";
import { TemplateDynamicComponent } from "./template-dynamic/template-dynamic.component";

/** Available template containers to handle template rendering strategy */
export const TEMLATE_CONTAINERS = {
default: TemplateContainerComponent,
dynamic: TemplateDynamicComponent,
} as const;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<!-- Template row host elements -->
<ng-template ngFor let-row [ngForOf]="renderedRows" let-i="index" [ngForTrackBy]="trackByFn">
<ng-template templateHost></ng-template>
</ng-template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from "@angular/core/testing";

import { TemplateDynamicComponent } from "./template-dynamic.component";

describe("TemplateDynamicComponent", () => {
let component: TemplateDynamicComponent;
let fixture: ComponentFixture<TemplateDynamicComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [TemplateDynamicComponent],
}).compileComponents();

fixture = TestBed.createComponent(TemplateDynamicComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it("should create", () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import {
Component,
Input,
QueryList,
ViewChildren,
ChangeDetectorRef,
ChangeDetectionStrategy,
OnInit,
Type,
} from "@angular/core";
import { TEMPLATE_COMPONENT_MAPPING } from "../../components";
import { TemplateHostDirective } from "../../directives/templateHost.directive";
import { FlowTypes, ITemplateRowProps } from "../../models";

@Component({
selector: "plh-template-dynamic",
templateUrl: "./template-dynamic.component.html",
styleUrls: ["./template-dynamic.component.scss"],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TemplateDynamicComponent implements OnInit {
public renderedRows: BaseRow[] = [];

/** */
@ViewChildren(TemplateHostDirective, { read: TemplateHostDirective })
rowHosts: QueryList<TemplateHostDirective>;

@Input() template: FlowTypes.Template;

private allRows: BaseRow[] = [];

constructor(private cdr: ChangeDetectorRef) {}

ngOnInit() {
this.loadRows();
this.render();
}

private loadRows() {
this.allRows = this.template.rows.map((r) => {
const displayComponent = TEMPLATE_COMPONENT_MAPPING[r.type];
if (displayComponent) {
return new DisplayRowBase(r, displayComponent);
}
const structuralCommponent = STRUCTURAL_ROW_MAPPING[r.type];
if (structuralCommponent) {
return new structuralCommponent(r);
}
return new BaseRow(r);
});
}

private render() {
// evaluate rows to determine number of display components to render
const renderedRows = [];
for (const row of this.allRows) {
const { rendered } = row.evaluate();
if (rendered) {
renderedRows.push(row);
}
}
this.renderedRows = renderedRows;
this.cdr.detectChanges();
// render components
this.rowHosts.map((host, index) => {
this.renderedRows[index].render(host);
});
this.cdr.detectChanges();
}

trackByFn(index) {
return index;
}
}

/***************************************************************
* Row Renderers
**************************************************************/

class BaseRow {
public rendered: Boolean = false;
public renderComponents: any[] = [];
public render(host: TemplateHostDirective) {}
private listeners() {
// TODO - find a way to attach dynamic variables (or in parent) and queue re-render as required
}

private children?: BaseRow[];

constructor(public row: FlowTypes.TemplateRow) {}

public evaluate() {
this.rendered = true;

if (this.children) {
// TODO
}
return this;
}
}

class DisplayRowBase extends BaseRow {
private renderCount = 0;

public renderID: string;

constructor(
public row: FlowTypes.TemplateRow,
private displayComponent: Type<ITemplateRowProps>
) {
super(row);
}

public override render(host: TemplateHostDirective) {
this.renderCount++;
const viewContainerRef = host.viewContainerRef;
viewContainerRef.clear();
const componentRef = viewContainerRef.createComponent(this.displayComponent);
componentRef.instance.row = this.row;
componentRef.instance.parent = null;
}
}
class TemplateRow extends BaseRow {}

class SetLocalRow extends BaseRow {}

// TODO - better if distinction between display/structural component made at origingal type-def and
// folder levels. Also should move into components/structural folder

const STRUCTURAL_ROW_MAPPING: PartialRecord<FlowTypes.TemplateRowType, typeof BaseRow> = {
set_local: SetLocalRow,
template: TemplateRow,
};

type PartialRecord<K extends keyof any, T> = {
[P in K]?: T;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Directive, ViewContainerRef } from "@angular/core";

/**
* Provides reference for injecting template
* @example component.html
* ```ts
* <div templateHost></div>
* ```
*
* @example component.ts
* ```ts
* @ViewChild(TemplateHostDirective, { static: true }) templateHost!: TemplateHostDirective;
*
* ngOnInit(){
* this.templateService.injectTemplate(name,this.templateHost)
* }
* ```
*/
@Directive({ selector: "[templateHost]" })
export class TemplateHostDirective {
constructor(public viewContainerRef: ViewContainerRef) {}
}
21 changes: 21 additions & 0 deletions src/app/shared/components/template/services/template.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { TemplateVariablesService } from "./template-variables.service";
import { TemplateFieldService } from "./template-field.service";
import { arrayToHashmap } from "src/app/shared/utils";
import { SyncServiceBase } from "src/app/shared/services/syncService.base";
import { TemplateHostDirective } from "../directives/templateHost.directive";
import { TEMLATE_CONTAINERS } from "../containers";

@Injectable({
providedIn: "root",
Expand Down Expand Up @@ -85,6 +87,25 @@ export class TemplateService extends SyncServiceBase {
return { modal, ...dismissData };
}

/** Dynamically inject a template within a given host container */
public async injectTemplate(name: string, host: TemplateHostDirective) {
const template = await this.getTemplateByName(name, false);
const viewContainerRef = host.viewContainerRef;
viewContainerRef.clear();
// WiP - Experimental new format
if (template.template_type === "dynamic") {
const container = TEMLATE_CONTAINERS.dynamic;
const componentRef = viewContainerRef.createComponent(container);
componentRef.instance.template = template;
}
// Standard template container
else {
const container = TEMLATE_CONTAINERS.default;
const componentRef = viewContainerRef.createComponent(container);
componentRef.instance.templatename = name;
}
}

/**
* Iterate over global template rows, assigning `declare_field_default` values to fields if they do not already exist,
* and `declare_global_constant` values to global regardless.
Expand Down
14 changes: 12 additions & 2 deletions src/app/shared/components/template/template.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ import { TmplCompHostDirective, TemplateComponent } from "./template-component";

import { appendStyleSvgDirective } from "./directives/shadowStyleSvg.directive";
import { createCustomElement } from "@angular/elements";
import { TemplateDynamicComponent } from "./containers/template-dynamic/template-dynamic.component";
import { TemplateHostDirective } from "./directives/templateHost.directive";

const TEMPLATE_DIRECTIVES = [TemplateHostDirective, appendStyleSvgDirective];

@NgModule({
imports: [
Expand All @@ -29,15 +33,21 @@ import { createCustomElement } from "@angular/elements";
RouterModule,
SwiperModule,
],
exports: [...TEMPLATE_COMPONENTS, ...TEMPLATE_PIPES, TemplateContainerComponent],
exports: [
...TEMPLATE_COMPONENTS,
...TEMPLATE_DIRECTIVES,
...TEMPLATE_PIPES,
TemplateContainerComponent,
],
declarations: [
TmplCompHostDirective,
TemplateComponent,
TooltipDirective,
...TEMPLATE_COMPONENTS,
...TEMPLATE_DIRECTIVES,
...TEMPLATE_PIPES,
TemplateContainerComponent,
appendStyleSvgDirective,
TemplateDynamicComponent,
],
// Include the container component as an entry component so that we can a custom elements for it (see below)
entryComponents: [TemplateContainerComponent],
Expand Down