Skip to content

Commit

Permalink
Merge pull request #2320 from IDEMSInternational/refactor/app-fields
Browse files Browse the repository at this point in the history
Refactor!: app field settings
  • Loading branch information
esmeetewinkel authored May 30, 2024
2 parents 49ff67b + bfcfebf commit 4fb5cdc
Show file tree
Hide file tree
Showing 19 changed files with 205 additions and 190 deletions.
30 changes: 0 additions & 30 deletions packages/data-models/appConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,27 +25,6 @@ const DYNAMIC_PREFIXES = [
"raw",
] as const;

/**
* All localstorage fields will be prefixed with this
* TODO - this has not been consistently applied so refactoring required
* */
const FIELD_PREFIX = "rp-contact-field";

/**
* Fieldnames hardcoded into the app
* TODO - these have not been consistently applied so refactoring required
* */
const APP_FIELDS = {
APP_AUTH_USER: `${FIELD_PREFIX}._app_auth_user`,
APP_LANGUAGE: `${FIELD_PREFIX}._app_language`,
APP_SKIN: `${FIELD_PREFIX}._app_skin`,
APP_THEME: `${FIELD_PREFIX}._app_theme`,
APP_VERSION: `${FIELD_PREFIX}._app_version`,
CONTENT_VERSION: `${FIELD_PREFIX}._content_version`,
DEPLOYMENT_NAME: `${FIELD_PREFIX}._deployment_name`,
SERVER_SYNC_LATEST: `${FIELD_PREFIX}._server_sync_latest`,
};

const APP_LANGUAGES = {
/** Language used during first load. If translations do not exist will default to source strings (gb_en) */
default: "gb_en",
Expand Down Expand Up @@ -189,9 +168,6 @@ const FEEDBACK_MODULE_DEFAULTS = {
displayedTemplate: "feature_feedback_text_select",
},
],
/** Field to populate with selected text for use in templates */
selected_text_field: "_feedback_selected_text",
sidebar_open_field: "_feedback_sidebar_open",
};

const APP_UPDATES = {
Expand All @@ -203,10 +179,6 @@ const APP_UPDATES = {
* If no template is provided provided, installation of the downloaded flexible update will be completed on next app init
*/
completeUpdateTemplate: "app_update_complete",
/** Track whether an update is available for download */
app_update_available_field: "_app_update_available",
/** Track whether an update has been downloaded and is available for install */
app_update_downloaded_field: "_app_update_downloaded",
};

const ASSET_PACKS = {
Expand All @@ -225,7 +197,6 @@ const TASKS = {
};

const APP_CONFIG = {
APP_FIELDS,
APP_HEADER_DEFAULTS,
APP_INITIALISATION_DEFAULTS,
APP_AUTHENTICATION_DEFAULTS,
Expand All @@ -241,7 +212,6 @@ const APP_CONFIG = {
ASSET_PACKS,
DYNAMIC_PREFIXES,
FEEDBACK_MODULE_DEFAULTS,
FIELD_PREFIX,
NOTIFICATIONS_SYNC_FREQUENCY_MS,
NOTIFICATION_DEFAULTS,
SERVER_SYNC_FREQUENCY_MS,
Expand Down
27 changes: 27 additions & 0 deletions packages/data-models/fields.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Protected fields can be set by field service and will be prefixed with an underscore
* They are used to store store computed or exported variables and are not user overridable
*/
enum PROTECTED_FIELDS {
APP_AUTH_USER = "app_auth_user",
APP_FIRST_LAUNCH = "app_first_launch",
APP_LANGUAGE = "app_language",
APP_SKIN = "app_skin",
APP_THEME = "app_theme",
/** Track whether an update is available for download */
APP_UPDATE_AVAILABLE = "app_update_available",
/** Track whether an update has been downloaded and is available for install */
APP_UPDATE_DOWNLOADED = "app_update_downloaded",
APP_USER_ID = "app_user_id",
APP_VERSION = "app_version",
CONTENT_VERSION = "content_version",
DEPLOYMENT_NAME = "deployment_name",
FEEDBACK_SELECTED_TEXT = "feedback_selected_text",
FEEDBACK_SIDEBAR_OPEN = "feedback_sidebar_open",
SERVER_SYNC_LATEST = "server_sync_latest",
}

/** Whenever retrieving a protected field make sure to include underscore prefix */
export const getProtectedFieldName = (key: IProtectedFieldName) => `_${PROTECTED_FIELDS[key]}`;

export type IProtectedFieldName = keyof typeof PROTECTED_FIELDS;
1 change: 1 addition & 0 deletions packages/data-models/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from "./appConfig";
export * from "./db.model";
export * from "./deployment.model";
export * from "./fields";
export * from "./flowTypes";
export * from "./functions";
export * from "./skin.model";
24 changes: 16 additions & 8 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { SyncServiceBase } from "./shared/services/syncService.base";
import { SeoService } from "./shared/services/seo/seo.service";
import { FeedbackService } from "./feature/feedback/feedback.service";
import { ShareService } from "./shared/services/share/share.service";
import { LocalStorageService } from "./shared/services/local-storage/local-storage.service";

@Component({
selector: "app-root",
Expand All @@ -51,7 +52,6 @@ export class AppComponent {
CONTENT_VERSION = environment.deploymentConfig.git.content_tag_latest;
DEPLOYMENT_NAME = environment.deploymentName;
appConfig: IAppConfig;
appFields: IAppConfig["APP_FIELDS"];
appAuthenticationDefaults: IAppConfig["APP_AUTHENTICATION_DEFAULTS"];
sideMenuDefaults: IAppConfig["APP_SIDEMENU_DEFAULTS"];
footerDefaults: IAppConfig["APP_FOOTER_DEFAULTS"];
Expand All @@ -72,6 +72,7 @@ export class AppComponent {
private menuController: MenuController,
private router: Router,
// App services
private localStorageService: LocalStorageService,
private skinService: SkinService,
private appConfigService: AppConfigService,
private dynamicDataService: DynamicDataService,
Expand Down Expand Up @@ -113,11 +114,7 @@ export class AppComponent {
this.platform.ready().then(async () => {
this.platforms = this.platform.platforms().join(" ");
this.subscribeToAppConfigChanges();

// ensure deployment field set correctly for use in any startup services or templates
localStorage.setItem(this.appFields.DEPLOYMENT_NAME, this.DEPLOYMENT_NAME);
localStorage.setItem(this.appFields.APP_VERSION, this.APP_VERSION);
localStorage.setItem(this.appFields.CONTENT_VERSION, this.CONTENT_VERSION);
await this.populateAppInitFields();
await this.initialiseCoreServices();
this.hackSetDeveloperOptions();
const isDeveloperMode = this.templateFieldService.getField("user_mode") === false;
Expand Down Expand Up @@ -149,6 +146,18 @@ export class AppComponent {
this.scheduleReinitialisation();
});
}
/** Populate contact fields that may be used by other services during initialisation */
private async populateAppInitFields() {
this.localStorageService.setProtected("DEPLOYMENT_NAME", this.DEPLOYMENT_NAME);
this.localStorageService.setProtected("APP_VERSION", this.APP_VERSION);
this.localStorageService.setProtected("CONTENT_VERSION", this.CONTENT_VERSION);
// HACK - ensure first_app_launch migrated from event service
if (!this.localStorageService.getProtected("APP_FIRST_LAUNCH")) {
await this.appEventService.ready();
const { first_app_launch } = this.appEventService.summary;
this.localStorageService.setProtected("APP_FIRST_LAUNCH", first_app_launch);
}
}

/**
* Authentication requires verified domain and app ids populated to firebase console
Expand Down Expand Up @@ -180,7 +189,6 @@ export class AppComponent {
this.sideMenuDefaults = this.appConfig.APP_SIDEMENU_DEFAULTS;
this.footerDefaults = this.appConfig.APP_FOOTER_DEFAULTS;
this.appAuthenticationDefaults = this.appConfig.APP_AUTHENTICATION_DEFAULTS;
this.appFields = this.appConfig.APP_FIELDS;
});
}

Expand Down Expand Up @@ -313,7 +321,7 @@ export class AppComponent {
if (location.hostname === "localhost" && !environment.production) {
const isUserMode = this.templateFieldService.getField("user_mode");
if (isUserMode !== false) {
this.templateFieldService.setField("user_mode", "false");
this.localStorageService.setString("user_mode", "false");
}
}
}
Expand Down
17 changes: 4 additions & 13 deletions src/app/feature/feedback/feedback.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import { UserMetaService } from "src/app/shared/services/userMeta/userMeta.servi
import { TemplateService } from "src/app/shared/components/template/services/template.service";
import { generateTimestamp } from "src/app/shared/utils";
import { environment } from "src/environments/environment";
import { TemplateFieldService } from "src/app/shared/components/template/services/template-field.service";
import { DbService } from "src/app/shared/services/db/db.service";
import { DBSyncService } from "src/app/shared/services/db/db-sync.service";
import {
Expand All @@ -38,6 +37,7 @@ import {
TemplateActionRegistry,
} from "src/app/shared/components/template/services/instance/template-action.registry";
import { SyncServiceBase } from "src/app/shared/services/syncService.base";
import { LocalStorageService } from "src/app/shared/services/local-storage/local-storage.service";

@Injectable({
providedIn: "root",
Expand Down Expand Up @@ -71,7 +71,7 @@ export class FeedbackService extends SyncServiceBase {
constructor(
private contextMenuService: ContextMenuService,
private templateService: TemplateService,
private templateFieldService: TemplateFieldService,
private localStorageService: LocalStorageService,
private userMetaService: UserMetaService,
private toastController: ToastController,
private dbService: DbService,
Expand Down Expand Up @@ -183,19 +183,12 @@ export class FeedbackService extends SyncServiceBase {
}

public async sidebarOpen() {
await this.setSidebarField(true);
this.router.navigate([{ outlets: { sidebar: ["feedback"] } }]);
}
public async sidebarClose() {
await this.setSidebarField(false);
this.router.navigate([{ outlets: { sidebar: [] } }]);
}

private async setSidebarField(isOpen: boolean) {
const { sidebar_open_field } = this.feedbackModuleDefaults;
await this.templateFieldService.setField(sidebar_open_field, `${isOpen}`);
}

/**
* Create a standalone popup of the provided template and use to collect user feedback.
* Modal dismiss and feedback retrieval will be handled by the feedback actions handlers
Expand Down Expand Up @@ -332,17 +325,15 @@ export class FeedbackService extends SyncServiceBase {
feedbackButton: IFeedbackContextMenuButton,
contextData: IContextMenuActionData = {}
) {
// set selected text to field for access in templates
const { selected_text_field } = this.feedbackModuleDefaults;
if (contextData?.selectedText) {
await this.templateFieldService.setField(selected_text_field, contextData.selectedText);
this.localStorageService.setProtected("FEEDBACK_SELECTED_TEXT", contextData.selectedText);
}
// launch feedback template, disable feedback mode to prevent actions on feedback poup
const additional = { ...contextData, id: feedbackButton.id };
await this.runFeedbackTemplate(feedbackButton.displayedTemplate, additional, ev);

// clear previously set field
await this.templateFieldService.setField(selected_text_field, null);
this.localStorageService.setProtected("FEEDBACK_SELECTED_TEXT", null);
}

/**
Expand Down
6 changes: 2 additions & 4 deletions src/app/feature/theme/services/theme.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { SyncServiceBase } from "src/app/shared/services/syncService.base";
export class ThemeService extends SyncServiceBase {
currentTheme$ = new BehaviorSubject<string>(null);
availableThemes: IAppConfig["APP_THEMES"]["available"];
appFields: IAppConfig["APP_FIELDS"];
defaultThemeName: string;

constructor(
Expand Down Expand Up @@ -41,14 +40,14 @@ export class ThemeService extends SyncServiceBase {
document.body.dataset.theme = themeName;
this.currentTheme$.next(themeName);
// Use local storage so that the current theme persists across app launches
this.localStorageService.setString(this.appFields.APP_THEME, themeName);
this.localStorageService.setProtected("APP_THEME", themeName);
} else {
console.error(`No theme found with name "${themeName}"`);
}
}

public getCurrentTheme() {
return this.localStorageService.getString(this.appFields.APP_THEME);
return this.localStorageService.getProtected("APP_THEME");
}

/** Calculate all custom properties inherited for a particular element */
Expand Down Expand Up @@ -91,7 +90,6 @@ export class ThemeService extends SyncServiceBase {

subscribeToAppConfigChanges() {
this.appConfigService.appConfig$.subscribe((appConfig: IAppConfig) => {
this.appFields = appConfig.APP_FIELDS;
this.availableThemes = appConfig.APP_THEMES.available;
this.defaultThemeName = appConfig.APP_THEMES.defaultThemeName;
});
Expand Down
17 changes: 7 additions & 10 deletions src/app/feature/user/pages/user-debug/user-debug.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Component, Injector, OnInit } from "@angular/core";
import { TemplateActionService } from "src/app/shared/components/template/services/instance/template-action.service";
import { TemplateFieldService } from "src/app/shared/components/template/services/template-field.service";
import { DynamicDataService } from "src/app/shared/services/dynamic-data/dynamic-data.service";
import { LocalStorageService } from "src/app/shared/services/local-storage/local-storage.service";

interface IDynamicDataEntry {
id: string;
Expand All @@ -19,6 +20,7 @@ export class UserDebugPage implements OnInit {
constructor(
private fieldService: TemplateFieldService,
private dynamicDataService: DynamicDataService,
private localStorageService: LocalStorageService,
private injector: Injector
) {}
/** Id of current user */
Expand Down Expand Up @@ -87,16 +89,11 @@ export class UserDebugPage implements OnInit {

/** Retrieve localStorage entries prefixed by field service prefix */
private getUserContactFields() {
const contactFields: { key: string; value: string }[] = [];
const prefix = `${this.fieldService.prefix}.`;
for (const key in localStorage) {
if (key.startsWith(prefix)) {
contactFields.push({
key: key.replace(prefix, ""),
value: localStorage.getItem(key),
});
}
}
const localStorageHashmap = this.localStorageService.getAll();
const contactFields = Object.entries<string>(localStorageHashmap).map(([key, value]) => ({
key,
value,
}));
return contactFields.sort((a, b) => (a.key > b.key ? 1 : -1));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@ import { AppConfigService } from "src/app/shared/services/app-config/app-config.
export class TemplateFieldService extends AsyncServiceBase {
globals: { [name: string]: FlowTypes.GlobalRow } = {};

/** App config prefix used */
public prefix: string;

constructor(
private localStorageService: LocalStorageService,
private dbService: DbService,
Expand All @@ -26,7 +23,6 @@ export class TemplateFieldService extends AsyncServiceBase {
super("TemplateField");
this.registerInitFunction(this.initialise);
this.registerTemplateActionHandlers();
this.prefix = appConfigService.APP_CONFIG.FIELD_PREFIX;
}

private async initialise() {
Expand All @@ -50,12 +46,12 @@ export class TemplateFieldService extends AsyncServiceBase {
}

/**
* Retrieve fields from localstorage. These are automatically prefixed with 'rp-contact-field'
* and will be returned as string or boolean
* Retrieve fields from localstorage and return as string or boolean
* TODO - ideally showWarnings should be linked to some sort of debug mode
*/
public getField(key: string, showWarnings = true) {
let val: any = this.localStorageService.getString(`${this.prefix}.${key}`);
if (!key) return undefined;
let val: any = this.localStorageService.getString(key);
// provide a fallback if the target variable does not exist in local storage
if (val === null && showWarnings) {
// console.warn("field value not found for key:", key);
Expand Down Expand Up @@ -86,7 +82,7 @@ export class TemplateFieldService extends AsyncServiceBase {
}
}
// write to local storage - this will cast to string
this.localStorageService.setString(`${this.prefix}.${key}`, value);
this.localStorageService.setString(key, value);

// write to db - note this can handle more data formats but only string/number will be available to queries
if (typeof value === "boolean") value = "value";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ export class TemplateTranslateService extends AsyncServiceBase {
* Formatted as country-language code, e.g. za-en
**/
app_language$ = new BehaviorSubject<string>(null);
appFields: IAppConfig["APP_FIELDS"];
appLanguages: IAppConfig["APP_LANGUAGES"];
appLanguagesMeta: IAppConfig["APP_LANGUAGES_META"];
languageDirection: WritableSignal<"ltr" | "rtl"> = signal("ltr");
Expand All @@ -41,7 +40,7 @@ export class TemplateTranslateService extends AsyncServiceBase {
this.appConfigService,
]);
this.subscribeToAppConfigChanges();
const currentLanguage = this.localStorageService.getString(this.appFields.APP_LANGUAGE);
const currentLanguage = this.localStorageService.getProtected("APP_LANGUAGE");
if (currentLanguage) {
await this.setLanguage(currentLanguage, false);
} else {
Expand All @@ -58,7 +57,7 @@ export class TemplateTranslateService extends AsyncServiceBase {
if (code) {
console.log("[SET LANGUAGE]", code);
if (updateDB) {
this.localStorageService.setString(this.appFields.APP_LANGUAGE, code);
this.localStorageService.setProtected("APP_LANGUAGE", code);
}
const translationStrings = await this.appDataService.getTranslationStrings(code);
this.translation_strings = translationStrings || {};
Expand Down Expand Up @@ -143,7 +142,6 @@ export class TemplateTranslateService extends AsyncServiceBase {

subscribeToAppConfigChanges() {
this.appConfigService.appConfig$.subscribe((appConfig: IAppConfig) => {
this.appFields = appConfig.APP_FIELDS;
this.appLanguages = appConfig.APP_LANGUAGES;
this.appLanguagesMeta = appConfig.APP_LANGUAGES_META;
});
Expand Down
Loading

0 comments on commit 4fb5cdc

Please sign in to comment.