From bc6fa0cd2cfc48320c33b0a17107921faf5f3f59 Mon Sep 17 00:00:00 2001 From: Nick Stokoe Date: Tue, 19 Mar 2024 17:19:43 +0000 Subject: [PATCH 1/8] .prettierignore - tell Prettier not to reformat any of the code. Prettier is a code linker/formatter with strong opinions and little configurability, used by some people on this project. --- .prettierignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .prettierignore diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..fa29cdff --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +** \ No newline at end of file From 58bdd1592921dd0e42985a1f528cbeb46be365cd Mon Sep 17 00:00:00 2001 From: Nick Stokoe Date: Tue, 19 Mar 2024 17:25:24 +0000 Subject: [PATCH 2/8] app/model/config-schema.ts - remove trailing semis from class/interface defs A purely stylistic change. --- src/map-app/app/model/config-schema.ts | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/map-app/app/model/config-schema.ts b/src/map-app/app/model/config-schema.ts index e2d7bf02..9c5df2b7 100644 --- a/src/map-app/app/model/config-schema.ts +++ b/src/map-app/app/model/config-schema.ts @@ -54,7 +54,7 @@ class TypeDef { stringDescr?: string; // A string-parsing function parseString?: (val: string) => T; -}; +} export interface VocabSource { @@ -84,7 +84,7 @@ export interface DataSource { id: string; type: string; label: string; -}; +} export interface HostSparqlDataSource extends DataSource { type: 'hostSparql'; @@ -144,7 +144,7 @@ export interface ReadableConfig { htmlTitle(): string; logo(): string | undefined; vocabularies(): AnyVocabSource[]; -}; +} export interface WritableConfig { setDefaultLatLng(val: Point2d): void; @@ -166,7 +166,7 @@ export interface WritableConfig { setShowDatasetsPanel(val: boolean): void; setShowDirectoryPanel(val: boolean): void; setShowSearchPanel(val: boolean): void; -}; +} export interface ConfigSchema { // An identifier string @@ -187,7 +187,7 @@ export interface DialogueSize { width?: string; height?: string; descriptionRatio?: number; -}; +} export type InitiativeRenderFunction = (initiative: Initiative, model: DataServices) => string; @@ -247,12 +247,12 @@ export class ConfigData { constructor(params: Partial = {}) { Object.assign(this, params); } -}; +} // This type is constrained to have the same keys as ConfigData, and // values which are ConfigSchema of the appropriate type for the // ConfigData property in question. -export type ConfigSchemas = { [K in keyof ConfigData]: ConfigSchema }; +export type ConfigSchemas = { [K in keyof ConfigData]: ConfigSchema } // Validates/normalises a language code. @@ -417,7 +417,7 @@ const types = { descr: 'An array of data source definitions, defining the type, ID, and in certain cases '+ 'other source-secific parameters needed for the source type', }), -}; +} @@ -1251,8 +1251,6 @@ ${def.descr} setShowSearchPanel(val: boolean): void { this.data.showSearchPanel = val; } - -// [id: string]: Getter | Setter; -}; - + // [id: string]: Getter | Setter; +} From 0083f2ece368af6b0543987dbccde0bed148b3bf Mon Sep 17 00:00:00 2001 From: Nick Stokoe Date: Tue, 19 Mar 2024 17:29:08 +0000 Subject: [PATCH 3/8] app/model/config-schema.ts - correct the desc: fields for some options Specifically - showAboutPanel and showDirectoryPanel. This fixes a cut-and-pasta error. --- src/map-app/app/model/config-schema.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/map-app/app/model/config-schema.ts b/src/map-app/app/model/config-schema.ts index 9c5df2b7..b3b8c9b5 100644 --- a/src/map-app/app/model/config-schema.ts +++ b/src/map-app/app/model/config-schema.ts @@ -724,7 +724,7 @@ export class Config implements ReadableConfig, WritableConfig { }, showAboutPanel: { id: 'showAboutPanel', - descr: `If true this will load the datasets panel`, + descr: `If true this will load the about panel`, getter: 'getShowAboutPanel', setter: 'setShowAboutPanel', type: types.boolean, @@ -738,14 +738,14 @@ export class Config implements ReadableConfig, WritableConfig { }, showDirectoryPanel: { id: 'showDirectoryPanel', - descr: `If true this will load the datasets panel`, + descr: `If true this will load the directory panel`, getter: 'getShowDirectoryPanel', setter: 'setShowDirectoryPanel', type: types.boolean, }, showSearchPanel: { id: 'showSearchPanel', - descr: `If true this will load the datasets panel`, + descr: `If true this will load the initiatives (i.e. search) panel`, getter: 'getShowSearchPanel', setter: 'setShowSearchPanel', type: types.boolean, From 76cf7e439f92eaced7b4812e3c06148bac7a861e Mon Sep 17 00:00:00 2001 From: rogup Date: Tue, 5 Mar 2024 15:51:01 +0000 Subject: [PATCH 4/8] Add defaultPanel option to config Then disable prettier then redo changes to config schema --- src/map-app/app/model/config-schema.ts | 19 ++++++++++++++++++- src/map-app/app/presenter/sidebar.ts | 8 ++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/map-app/app/model/config-schema.ts b/src/map-app/app/model/config-schema.ts index b3b8c9b5..76da9d96 100644 --- a/src/map-app/app/model/config-schema.ts +++ b/src/map-app/app/model/config-schema.ts @@ -135,6 +135,7 @@ export interface ReadableConfig { getShowDatasetsPanel(): boolean; getShowDirectoryPanel(): boolean; getShowSearchPanel(): boolean; + getDefaultPanel(): string; getSidebarButtonColour(): string; getSoftwareGitCommit(): string; getSoftwareTimestamp(): string; @@ -166,6 +167,7 @@ export interface WritableConfig { setShowDatasetsPanel(val: boolean): void; setShowDirectoryPanel(val: boolean): void; setShowSearchPanel(val: boolean): void; + setDefaultPanel(val: string): void; } export interface ConfigSchema { @@ -235,6 +237,7 @@ export class ConfigData { showDatasetsPanel: boolean = true; showDirectoryPanel: boolean = true; showSearchPanel: boolean = true; + defaultPanel: string = ''; sidebarButtonColour: string = '#39cccc'; tileUrl?: string; timestamp: string = '2000-01-01T00:00:00.000Z'; @@ -750,6 +753,14 @@ export class Config implements ReadableConfig, WritableConfig { setter: 'setShowSearchPanel', type: types.boolean, }, + defaultPanel: { + id: "defaultPanel", + descr: "The string `about`, `directory`, `datasets`, or `initiatives` (i.e. search)", + defaultDescr: "the leftmost panel, which is usually the `directory`", + getter: "getDefaultPanel", + setter: "setDefaultPanel", + type: types.string, + }, sidebarButtonColour: { id: "sidebarButtonColour", descr: 'Set the css background-colour attribute for the open sidebar button. Defaults to teal', @@ -1150,6 +1161,9 @@ ${def.descr} getShowSearchPanel(): boolean { return this.data.showSearchPanel; } + getDefaultPanel(): string { + return this.data.defaultPanel; + } getSidebarButtonColour(): string { return this.data.sidebarButtonColour; } @@ -1251,6 +1265,9 @@ ${def.descr} setShowSearchPanel(val: boolean): void { this.data.showSearchPanel = val; } - + setDefaultPanel(val: string): void { + this.data.defaultPanel = val; + } + // [id: string]: Getter | Setter; } diff --git a/src/map-app/app/presenter/sidebar.ts b/src/map-app/app/presenter/sidebar.ts index 461b8792..4c880a9b 100644 --- a/src/map-app/app/presenter/sidebar.ts +++ b/src/map-app/app/presenter/sidebar.ts @@ -24,11 +24,15 @@ export class SidebarPresenter extends BasePresenter { this.showSearchPanel = mapui.config.getShowSearchPanel(); this.showAboutPanel = mapui.config.getShowAboutPanel(); this.showDatasetsPanel = mapui.config.getShowDatasetsPanel(); - this.view = new SidebarView(this, mapui.dataServices.getSidebarButtonColour()); + const defaultPanel = mapui.config.getDefaultPanel() || undefined; + this.view = new SidebarView( + this, + mapui.dataServices.getSidebarButtonColour() + ); this._eventbusRegister(); this.createSidebars(); - this.changeSidebar(); + this.changeSidebar(defaultPanel); } createSidebars() { From 3e8f2c6dc0e17c220165e1947ad9a2a1c0175f9a Mon Sep 17 00:00:00 2001 From: Nick Stokoe Date: Tue, 19 Mar 2024 17:31:17 +0000 Subject: [PATCH 5/8] CONFIG.md - regenerate with `npm run generate-config-doc` --- CONFIG.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/CONFIG.md b/CONFIG.md index 1627fbc2..39c65061 100644 --- a/CONFIG.md +++ b/CONFIG.md @@ -192,6 +192,18 @@ Set whether the sidebar is by default open on starting the app. +### `defaultPanel` + +- *type:* `{string}` +- *in string context:* parsed as-is +- *default:* the leftmost panel, which is usually the `directory` +- *settable?:* yes + +The string `about`, `directory`, `datasets`, or `initiatives` (i.e. search) + + + + ### `dialogueSize` - *type:* `{DialogueSize}` An object containing only string values. @@ -479,7 +491,7 @@ Preset location of the data source script(s). - *default:* `true` - *settable?:* yes -If true this will load the datasets panel +If true this will load the about panel @@ -503,7 +515,7 @@ If true this will load the datasets panel - *default:* `true` - *settable?:* yes -If true this will load the datasets panel +If true this will load the directory panel @@ -515,7 +527,7 @@ If true this will load the datasets panel - *default:* `true` - *settable?:* yes -If true this will load the datasets panel +If true this will load the initiatives (i.e. search) panel From 5522f6f35561fd6c550939e0a73615df0cdd4477 Mon Sep 17 00:00:00 2001 From: rogup Date: Tue, 12 Mar 2024 17:11:16 +0000 Subject: [PATCH 6/8] Add UT for defaultPanel config --- test/configs/typical/config.json | 4 +++- test/test-model-config.js | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/test/configs/typical/config.json b/test/configs/typical/config.json index 5113089c..99ad1678 100644 --- a/test/configs/typical/config.json +++ b/test/configs/typical/config.json @@ -19,5 +19,7 @@ "disableClusteringAtZoom": 10, "maxZoomOnGroup":12, "maxZoomOnOne": 14, - "maxZoomOnSearch": 12 + "maxZoomOnSearch": 12, + "defaultPanel": "about", + "defaultOpenSidebar": true } diff --git a/test/test-model-config.js b/test/test-model-config.js index 6f5f5bed..b8dbfe80 100644 --- a/test/test-model-config.js +++ b/test/test-model-config.js @@ -1,5 +1,4 @@ 'use strict'; -//const fs = require('fs'); import { assert } from 'chai'; import { init as configBuilder } from '../src/map-app/app/model/config'; @@ -8,7 +7,6 @@ import rawConfig from './configs/typical/config.json'; import version from './configs/typical/version.json'; const about = `This is a dummy about.html! `; -//fs.readFileSync('test/configs/typical/about.html'); const combinedConfig = { ...rawConfig, ...version, aboutHtml: about }; describe('The config.js module', function () { @@ -39,6 +37,8 @@ describe('The config.js module', function () { assert.equal(config.getMaxZoomOnOne(), 14); assert.equal(config.getMaxZoomOnSearch(), 12); assert.equal(config.logo(), undefined); + assert.equal(config.getDefaultPanel(), "about"); + assert.equal(config.getDefaultOpenSidebar(), true); }); }); }); From eed4296ba8692289bf500d055ebf90e16622b4a0 Mon Sep 17 00:00:00 2001 From: Nick Stokoe Date: Tue, 19 Mar 2024 21:35:39 +0000 Subject: [PATCH 7/8] app/{model/config-schema.ts,presenter/sidebar.ts} - make defaultPanel typesafe Use typescipt's typing to restrict the possible values of defaultPanel to one of the allowed ones. Ran into a problem triggering ERR_REQUIRE_ESM with the d3 library, just by changing the imports... turns out this is a thorny thing to solve, so avoided it by not importing the things. Instead I needed to list the allowed panel IDs in the documentation manually, rather than programatically. But ESM modules are "the future" so we probably need to convert Mykomap to support it (and support CommonJS for cases that need that, probably using Babel transpilation) --- src/map-app/app/model/config-schema.ts | 25 +++++--- src/map-app/app/presenter/sidebar.ts | 89 +++++++++++++++----------- 2 files changed, 70 insertions(+), 44 deletions(-) diff --git a/src/map-app/app/model/config-schema.ts b/src/map-app/app/model/config-schema.ts index 76da9d96..bc7a1de6 100644 --- a/src/map-app/app/model/config-schema.ts +++ b/src/map-app/app/model/config-schema.ts @@ -34,6 +34,7 @@ import type { import { Initiative, InitiativeObj } from './initiative'; import { isIso6391Code, Iso6391Code } from '../../localisations'; +import { SidebarId } from '../presenter/sidebar'; class TypeDef { constructor(params: { @@ -135,7 +136,7 @@ export interface ReadableConfig { getShowDatasetsPanel(): boolean; getShowDirectoryPanel(): boolean; getShowSearchPanel(): boolean; - getDefaultPanel(): string; + getDefaultPanel(): SidebarId; getSidebarButtonColour(): string; getSoftwareGitCommit(): string; getSoftwareTimestamp(): string; @@ -167,7 +168,7 @@ export interface WritableConfig { setShowDatasetsPanel(val: boolean): void; setShowDirectoryPanel(val: boolean): void; setShowSearchPanel(val: boolean): void; - setDefaultPanel(val: string): void; + setDefaultPanel(val: SidebarId): void; } export interface ConfigSchema { @@ -237,7 +238,7 @@ export class ConfigData { showDatasetsPanel: boolean = true; showDirectoryPanel: boolean = true; showSearchPanel: boolean = true; - defaultPanel: string = ''; + defaultPanel: SidebarId = 'directory'; sidebarButtonColour: string = '#39cccc'; tileUrl?: string; timestamp: string = '2000-01-01T00:00:00.000Z'; @@ -409,6 +410,14 @@ const types = { name: '{InitiativeRenderFunction}', descr: 'A function which accepts an Initiative instance and returns an HTML string', }), + sidebarId: new TypeDef({ + name: '{SidebarId}', + // Would be sensible to generate this from the keys of a `new SidebarPanels()` + // - except if we import that we hit ERR_REQUIRE_ESM because d3 is a pure ESM module. + // And this module is currently not pure ESM. Argh. And it's not trivial to switch! + // See, for example https://commerce.nearform.com/blog/2022/victory-esm/ + descr: 'One of these strings: "diretory", "initiatives", "about" or "datasets"' + }), vocabSources: new TypeDef({ name: '{AnyVocabSource[]}', descr: 'An array of vocab source definitions, defining a SPARQL endpoint URL, '+ @@ -755,11 +764,11 @@ export class Config implements ReadableConfig, WritableConfig { }, defaultPanel: { id: "defaultPanel", - descr: "The string `about`, `directory`, `datasets`, or `initiatives` (i.e. search)", - defaultDescr: "the leftmost panel, which is usually the `directory`", + descr: "Defines which panel opens by default.", + defaultDescr: "If unset, the default is 'directory'", getter: "getDefaultPanel", setter: "setDefaultPanel", - type: types.string, + type: types.sidebarId, }, sidebarButtonColour: { id: "sidebarButtonColour", @@ -1161,7 +1170,7 @@ ${def.descr} getShowSearchPanel(): boolean { return this.data.showSearchPanel; } - getDefaultPanel(): string { + getDefaultPanel(): SidebarId { return this.data.defaultPanel; } getSidebarButtonColour(): string { @@ -1265,7 +1274,7 @@ ${def.descr} setShowSearchPanel(val: boolean): void { this.data.showSearchPanel = val; } - setDefaultPanel(val: string): void { + setDefaultPanel(val: SidebarId): void { this.data.defaultPanel = val; } diff --git a/src/map-app/app/presenter/sidebar.ts b/src/map-app/app/presenter/sidebar.ts index 4c880a9b..49abde69 100644 --- a/src/map-app/app/presenter/sidebar.ts +++ b/src/map-app/app/presenter/sidebar.ts @@ -1,22 +1,35 @@ -import { Dictionary } from '../../common-types'; import { EventBus } from '../../eventbus'; import { MapUI } from '../map-ui'; import { SidebarView } from '../view/sidebar'; import { BasePresenter } from './base'; import { AboutSidebarPresenter } from './sidebar/about'; -import { BaseSidebarPresenter } from './sidebar/base'; import { DatasetsSidebarPresenter } from './sidebar/datasets'; import { DirectorySidebarPresenter } from './sidebar/directory'; import { InitiativesSidebarPresenter } from './sidebar/initiatives'; +/// A collection of sidebar panels, by name +export class SidebarPanels { + directory?: DirectorySidebarPresenter = undefined; + initiatives?: InitiativesSidebarPresenter = undefined; + about?: AboutSidebarPresenter = undefined; + datasets?: DatasetsSidebarPresenter = undefined; + + // A list of the IDs as a convenience + static readonly ids = Object.keys(new SidebarPanels()) as SidebarId[]; +} + +/// This type can contain only sidebar ID names +export type SidebarId = keyof SidebarPanels; + export class SidebarPresenter extends BasePresenter { readonly view: SidebarView; readonly showDirectoryPanel: boolean; readonly showSearchPanel: boolean; readonly showAboutPanel: boolean; readonly showDatasetsPanel: boolean; - private children: Dictionary = {}; - private sidebarName?: string; + private readonly children = new SidebarPanels(); + + private sidebarName?: SidebarId; constructor(readonly mapui: MapUI) { super(); @@ -24,20 +37,13 @@ export class SidebarPresenter extends BasePresenter { this.showSearchPanel = mapui.config.getShowSearchPanel(); this.showAboutPanel = mapui.config.getShowAboutPanel(); this.showDatasetsPanel = mapui.config.getShowDatasetsPanel(); - const defaultPanel = mapui.config.getDefaultPanel() || undefined; + const defaultPanel = mapui.config.getDefaultPanel(); this.view = new SidebarView( this, mapui.dataServices.getSidebarButtonColour() ); this._eventbusRegister(); - this.createSidebars(); - this.changeSidebar(defaultPanel); - } - - createSidebars() { - this.children = {}; - if(this.showingDirectory()) this.children.directory = new DirectorySidebarPresenter(this); @@ -49,39 +55,50 @@ export class SidebarPresenter extends BasePresenter { if(this.showingDatasets()) this.children.datasets = new DatasetsSidebarPresenter(this); + + this.changeSidebar(defaultPanel); } // Changes or refreshes the sidebar // - // @param name - the sidebar to change (needs to be one of the keys - // of this.sidebar) - changeSidebar(name?: string) { - if (name !== undefined) { - // Validate name - if (!(name in this.children)) { - console.warn(`ignoring request to switch to non-existant sidebar '${name}'`); - name = undefined; + // @param name - the sidebar to change + changeSidebar(name?: SidebarId): void { + if (!name) { + if (this.sidebarName) { + // Just refresh the currently showing sidebar. + this.children[this.sidebarName]?.refreshView(); + } + else { + // If nothing is showing, refresh the first in the list. Or nothing, if none. + let key: SidebarId; + for(key in this.children) { + const child = this.children[key]; + if (!child) + continue; + + this.sidebarName = key; + child.refreshView(); + break; + } } + return; } - if (name !== undefined) { - // If name is set, change the current sidebar and then refresh - this.sidebarName = name; - this.children[this.sidebarName]?.refreshView(); - } - else { - // Just refresh the currently showing sidebar. - // If nothing is showing, show the first. Or nothing, if none. - if (!this.sidebarName) { - const names = Object.keys(this.children); - if (names.length > 0) - this.sidebarName = names[0]; - else - return; // No sidebars? Can't do anything. + if (name in this.children) { + // A valid SidebarId. If it's present, change the current sidebar to that, and then refresh + const child = this.children[name]; + if (child !== undefined) { + this.sidebarName = name; + child.refreshView(); } - - this.children[this.sidebarName]?.refreshView(); + return; } + + // If we get here it's not a valid sidebar (possibly it wasn't configured) + console.warn( + "Attempting to call SidebarPresenter.changeSidebar() with a "+ + `non-existant sidebar '${name}' - ignoring.` + ); } showSidebar() { From 445aeb2330a61789b0779b81ed68e51890323d48 Mon Sep 17 00:00:00 2001 From: Nick Stokoe Date: Wed, 20 Mar 2024 17:07:51 +0000 Subject: [PATCH 8/8] CONFIG.md - npm run generate-config-doc --- CONFIG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONFIG.md b/CONFIG.md index 39c65061..a3e26ee4 100644 --- a/CONFIG.md +++ b/CONFIG.md @@ -194,12 +194,12 @@ Set whether the sidebar is by default open on starting the app. ### `defaultPanel` -- *type:* `{string}` +- *type:* `{SidebarId}` One of these strings: "diretory", "initiatives", "about" or "datasets" - *in string context:* parsed as-is -- *default:* the leftmost panel, which is usually the `directory` +- *default:* If unset, the default is 'directory' - *settable?:* yes -The string `about`, `directory`, `datasets`, or `initiatives` (i.e. search) +Defines which panel opens by default.