Skip to content

Commit

Permalink
Merge branch 'master' into feature-add-image-attribute-to-display-group
Browse files Browse the repository at this point in the history
  • Loading branch information
esmeetewinkel authored Dec 30, 2024
2 parents 740f4be + 9aba366 commit 9c1e6c9
Show file tree
Hide file tree
Showing 11 changed files with 92 additions and 93 deletions.
2 changes: 1 addition & 1 deletion documentation/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ colorama==0.4.4
ghp-import==2.0.2
idna==3.4
importlib-metadata==4.8.2
Jinja2==3.0.3
Jinja2==3.1.5
Markdown==3.3.6
MarkupSafe==2.0.1
mergedeep==1.3.4
Expand Down
44 changes: 27 additions & 17 deletions packages/data-models/appConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,24 +83,33 @@ const APP_ROUTE_DEFAULTS = {
],
};

export type IHeaderColourOptions = "primary" | "secondary" | "none";
export type IHeaderFooterBackgroundOptions = "primary" | "secondary" | "none";
/** The "compact" variant reduces the header height and removes the title */
export type IHeaderVariantOptions = "default" | "compact";

const APP_HEADER_DEFAULTS = {
title: "App",
interface IAppConfigHeader {
back_button: {
hidden?: boolean;
};
collapse: boolean;
colour: IHeaderFooterBackgroundOptions;
hidden?: boolean;
menu_button: {
hidden?: boolean;
};
template: string | null;
title: string;
variant: IHeaderVariantOptions;
}

const APP_HEADER_DEFAULTS: IAppConfigHeader = {
back_button: {},
collapse: false,
colour: "primary" as IHeaderColourOptions,
// The "compact" variant reduces the header height and removes the title
variant: "default" as IHeaderVariantOptions,
// default only show menu button on home screen
should_show_menu_button: (location: Location) =>
activeRoute(location) === APP_ROUTE_DEFAULTS.home_route,
// default show back button on all screens except home screen
should_show_back_button: (location: Location) =>
activeRoute(location) !== APP_ROUTE_DEFAULTS.home_route,
// on device minimize app when back button pressed from home screen
should_minimize_app_on_back: (location: Location) =>
activeRoute(location) === APP_ROUTE_DEFAULTS.home_route,
colour: "primary",
menu_button: {},
template: null,
title: "App",
variant: "default",
};

/**
Expand All @@ -113,8 +122,9 @@ const activeRoute = (location: Location) => {
return path;
};

const APP_FOOTER_DEFAULTS: { templateName: string | null } = {
templateName: null,
const APP_FOOTER_DEFAULTS = {
templateName: null as string | null,
background: "primary" as IHeaderFooterBackgroundOptions,
};

const LAYOUT = {
Expand Down
4 changes: 3 additions & 1 deletion packages/data-models/skin.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import { IAppConfigOverride } from "./appConfig";
* enabled: false
* }
* APP_HEADER_DEFAULTS: {
* should_show_menu_button: false
* menu_button: {
* hidden: true
* }
* }
* }
* }
Expand Down
18 changes: 10 additions & 8 deletions src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
[dir]="templateTranslateService.languageDirection()"
>
<!-- Left Sidebar -->
@if (sideMenuDefaults().enabled) {
@if (sideMenuConfig().enabled) {
<ion-menu #menu side="start" menuId="main-side-menu" contentId="main-content">
<ion-header>
<ion-toolbar color="primary">
<ion-title>{{ sideMenuDefaults().title }}</ion-title>
<ion-title>{{ sideMenuConfig().title }}</ion-title>
<div class="app-version" slot="end">
@if (sideMenuDefaults().should_show_version) {
@if (sideMenuConfig().should_show_version) {
@if (deploymentConfig._content_version; as CONTENT_VERSION) {
<span>
<abbr [title]="deploymentConfig._app_builder_version" tabindex="1">
Expand All @@ -22,15 +22,15 @@
<span>{{ deploymentConfig._app_builder_version }} </span>
}
}
@if (sideMenuDefaults().should_show_deployment_name) {
@if (sideMenuConfig().should_show_deployment_name) {
<span style="margin-left: 16px">({{ deploymentConfig.name }})</span>
}
</div>
</ion-toolbar>
</ion-header>
<ion-content>
<plh-template-container
[templatename]="sideMenuDefaults().template_name"
[templatename]="sideMenuConfig().template_name"
(click)="menu.close()"
[ignoreQueryParamChanges]="true"
></plh-template-container>
Expand All @@ -41,13 +41,15 @@
<!-- Main content: shows in split-pane when sidebar route active -->
<ion-split-pane when="lg" contentId="main" [disabled]="!sidebarRouter.isActivated">
<div style="display: flex; flex-direction: column; height: 100%; width: 100%" id="main">
<plh-main-header></plh-main-header>
<plh-main-header
[style.display]="headerConfig().hidden ? 'none' : 'block'"
></plh-main-header>
<div class="route-container" [ngStyle]="routeContainerStyle()">
<ion-router-outlet id="main-content"></ion-router-outlet>
</div>
@if (footerDefaults().templateName; as footerTemplateName) {
@if (footerConfig().templateName; as footerTemplateName) {
<ion-footer>
<ion-toolbar color="primary">
<ion-toolbar [color]="footerConfig().background">
<plh-template-container
[templatename]="footerTemplateName"
[ignoreQueryParamChanges]="true"
Expand Down
5 changes: 3 additions & 2 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,9 @@ import { TemplateMetadataService } from "./shared/components/template/services/t
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppComponent {
sideMenuDefaults = computed(() => this.appConfigService.appConfig().APP_SIDEMENU_DEFAULTS);
footerDefaults = computed(() => this.appConfigService.appConfig().APP_FOOTER_DEFAULTS);
headerConfig = computed(() => this.appConfigService.appConfig().APP_HEADER_DEFAULTS);
footerConfig = computed(() => this.appConfigService.appConfig().APP_FOOTER_DEFAULTS);
sideMenuConfig = computed(() => this.appConfigService.appConfig().APP_SIDEMENU_DEFAULTS);
layoutConfig = computed(() => this.appConfigService.appConfig().LAYOUT);

public routeContainerStyle = computed(() => {
Expand Down
15 changes: 8 additions & 7 deletions src/app/shared/components/header/header.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,16 @@
<ion-icon name="chevron-back-outline" slot="icon-only"></ion-icon>
</ion-button>
</ion-buttons>
<ng-container>
<ion-title
*ngIf="!(headerConfig().variant === 'compact')"
style="text-align: center"
routerLink="/"
>
@if (headerConfig().template) {
<plh-template-container
[templatename]="headerConfig().template"
[ignoreQueryParamChanges]="true"
></plh-template-container>
} @else if (headerConfig().title && headerConfig().variant !== "compact") {
<ion-title style="text-align: center" routerLink="/">
<span>{{ headerConfig().title }}</span>
</ion-title>
</ng-container>
}
<ion-buttons slot="end"> </ion-buttons>
</ion-toolbar>
</ion-header>
28 changes: 19 additions & 9 deletions src/app/shared/components/header/header.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import { Location } from "@angular/common";
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit, ViewChild } from "@angular/core";
import { computed, effect, signal } from "@angular/core";
import { toSignal } from "@angular/core/rxjs-interop";
import { NavigationEnd, NavigationStart, Router } from "@angular/router";
import { NavigationStart, Router } from "@angular/router";
import { App } from "@capacitor/app";
import { Capacitor, PluginListenerHandle } from "@capacitor/core";
import { Subscription, fromEvent, map } from "rxjs";
import type { IHeaderVariantOptions } from "data-models/appConfig";
import { AppConfigService } from "../../services/app-config/app-config.service";
import { IonHeader, ScrollBaseCustomEvent, ScrollDetail } from "@ionic/angular";
import { _wait } from "packages/shared/src/utils/async-utils";
import { activeRoute } from "../../utils/angular.utils";

interface ScrollCustomEvent extends ScrollBaseCustomEvent {
detail: ScrollDetail;
Expand Down Expand Up @@ -51,6 +52,9 @@ export class headerComponent implements OnInit, OnDestroy {
/** Track scroll events when using header collapse mode */
private scrollEvents$: Subscription;

/** Track whether on home route for back button and menu button side-effects */
private isHomeRoute = true;

constructor(
private router: Router,
private location: Location,
Expand All @@ -71,9 +75,7 @@ export class headerComponent implements OnInit, OnDestroy {
() => {
// when route changes handle side-effects
const e = this.routeChanges();
if (e instanceof NavigationEnd) {
this.handleRouteChange();
}
this.handleRouteChange();
if (e instanceof NavigationStart) {
this.hasBackHistory = true;
}
Expand Down Expand Up @@ -103,16 +105,24 @@ export class headerComponent implements OnInit, OnDestroy {

/** Determine whether to show back and menu buttons based on location */
private handleRouteChange() {
const { should_show_back_button, should_show_menu_button } = this.headerConfig();
this.showBackButton.set(should_show_back_button(location));
this.showMenuButton.set(should_show_menu_button(location));
// update whether home route or not
const { APP_ROUTE_DEFAULTS } = this.appConfigService.appConfig();
this.isHomeRoute = activeRoute(location) === APP_ROUTE_DEFAULTS.home_route;

const { back_button, menu_button } = this.headerConfig();

// The explicit `hidden` property should override the function of location
// TODO: move functions to component code and out of app config, no use case for overriding them
const showBackButton = !back_button.hidden && !this.isHomeRoute;
const showMenuButton = !menu_button.hidden && this.isHomeRoute;
this.showBackButton.set(showBackButton);
this.showMenuButton.set(showMenuButton);
this.marginTop.set(0);
}

/** When device back button evaluate conditions to handle app minimise */
private handleHardwareBackPress() {
const { should_minimize_app_on_back } = this.headerConfig();
if (should_minimize_app_on_back(location)) {
if (this.isHomeRoute) {
App.minimizeApp();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export function updateItemMeta(
}

// Apply recursively to ensure item children with nested rows (e.g. display groups) also inherit item context
// Do not override the item context for rows that create their own item lists (i.e. data_items and items)
if (r.rows && r.type !== "data_items" && r.type !== "items") {
r.rows = updateItemMeta(r.rows, itemData, dataListName);
}
Expand Down
8 changes: 5 additions & 3 deletions src/app/shared/components/template/template-component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,11 @@ export class TemplateComponent implements OnInit, AfterContentInit, ITemplateRow
if (this._row.type === "template") {
const componentRef = this.componentRef as ComponentRef<TemplateContainerComponent>;
if (componentRef.instance.parent) {
// HACK - if parent template changes name of nested child template instance fully
// recreate the template container
if ((componentRef.instance.templatename(), this._row.value)) {
// HACK - when a parent updates there may be cases when the updated variables need to be
// reflected in the child template (e.g. set_nested_variable contains parent local variable)
// As it is not currently easy to isolate this case, fully recreate any child template when
// the parent template updates
if (componentRef.instance.templatename()) {
this.componentRef.destroy();
this.renderTemplateComponent(this._row);
// TODO - test better ways to manage from container, confirming test cases from
Expand Down
50 changes: 5 additions & 45 deletions src/app/shared/services/skin/skin.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ const MOCK_SKIN_2: IAppSkin = {

const MOCK_APP_CONFIG: Partial<IAppConfig> = {
APP_HEADER_DEFAULTS: {
back_button: {},
menu_button: {},
template: null,
title: "default",
collapse: false,
colour: "none",
should_minimize_app_on_back: () => true,
should_show_back_button: () => true,
should_show_menu_button: () => true,
variant: "default",
},
APP_SKINS: {
Expand All @@ -50,6 +50,7 @@ const MOCK_APP_CONFIG: Partial<IAppConfig> = {
},
APP_FOOTER_DEFAULTS: {
templateName: "mock_footer",
background: "primary",
},
};

Expand Down Expand Up @@ -88,6 +89,7 @@ describe("SkinService", () => {
it("does not change non-overridden values", () => {
expect(service["appConfigService"].appConfig().APP_FOOTER_DEFAULTS).toEqual({
templateName: "mock_footer",
background: "primary",
});
});

Expand All @@ -96,55 +98,13 @@ describe("SkinService", () => {
expect(service.getActiveSkinName()).toEqual("MOCK_SKIN_2");
});

it("generates override and revert configs", () => {
expect(service["revertOverride"]).toEqual({
APP_HEADER_DEFAULTS: { title: "default", colour: "none" },
});
});

it("reverts previous override when applying another skin", () => {
// MOCK_SKIN_1 will already be applied on load
const override = service["generateOverrideConfig"](MOCK_SKIN_2);
// creates a deep merge of override properties on top of current
expect(override).toEqual({
APP_HEADER_DEFAULTS: {
// revert changes only available in skin_1
colour: "none",
// apply changes from skin_2
title: "mock 2",
variant: "compact",
},
});
const revert = service["generateRevertConfig"](MOCK_SKIN_2);

// creates config revert to undo just the skin changes
expect(revert).toEqual({
APP_HEADER_DEFAULTS: {
// only revert changes remaining from skin_2
title: "default",
variant: "default",
},
});
});

it("sets skin: sets active skin name", () => {
service["setSkin"](MOCK_SKIN_2.name);
expect(service.getActiveSkinName()).toEqual("MOCK_SKIN_2");
service["setSkin"](MOCK_SKIN_1.name);
expect(service.getActiveSkinName()).toEqual("MOCK_SKIN_1");
});

it("sets skin: sets revertOverride correctly", () => {
// MOCK_SKIN_1 will already be applied on load
service["setSkin"](MOCK_SKIN_2.name);
expect(service["revertOverride"]).toEqual({
APP_HEADER_DEFAULTS: {
title: "default",
variant: "default",
},
});
});

it("sets skin: updates AppConfigService.appConfig values", () => {
// MOCK_SKIN_1 will already be applied on load
service["setSkin"](MOCK_SKIN_2.name);
Expand Down
10 changes: 10 additions & 0 deletions src/app/shared/utils/angular.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,13 @@ export function ngRouterMergedSnapshot$(router: Router) {
startWith(mergeRouterSnapshots(router))
);
}

/**
* Utility function to return the active pathname without any sidebar routing e.g. /home(sidebar:alt)
* or basename when deployed to subfolder path, e.g. /my-repo/template/home (provided by <base href='' /> in head)
* */
export const activeRoute = (location: Location) => {
const baseHref = document.querySelector("base")?.getAttribute("href");
const path = location.pathname.replace(baseHref, "/").replace(/\(.+\)/, "");
return path;
};

0 comments on commit 9c1e6c9

Please sign in to comment.