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 diff --git a/CONFIG.md b/CONFIG.md index 880533ea..6cd553c3 100644 --- a/CONFIG.md +++ b/CONFIG.md @@ -97,6 +97,18 @@ Set whether the sidebar is by default open on starting the app. +### `defaultPanel` + +- *type:* `{SidebarId}` One of these strings: "diretory", "initiatives", "about" or "datasets" +- *in string context:* parsed as-is +- *default:* If unset, the default is 'directory' +- *settable?:* yes + +Defines which panel opens by default. + + + + ### `dialogueSize` - *type:* `{DialogueSize}` An object containing only string values. @@ -372,7 +384,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 @@ -396,7 +408,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 @@ -408,7 +420,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 diff --git a/src/map-app/app/model/config-schema.ts b/src/map-app/app/model/config-schema.ts index 531e021e..e168d15a 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: { @@ -54,7 +55,7 @@ class TypeDef { stringDescr?: string; // A string-parsing function parseString?: (val: string) => T; -}; +} export interface VocabSource { @@ -84,7 +85,7 @@ export interface DataSource { id: string; type: string; label: string; -}; +} export interface HostSparqlDataSource extends DataSource { type: 'hostSparql'; @@ -133,6 +134,7 @@ export interface ReadableConfig { getShowDatasetsPanel(): boolean; getShowDirectoryPanel(): boolean; getShowSearchPanel(): boolean; + getDefaultPanel(): SidebarId; getSidebarButtonColour(): string; getSoftwareGitCommit(): string; getSoftwareTimestamp(): string; @@ -142,7 +144,7 @@ export interface ReadableConfig { htmlTitle(): string; logo(): string | undefined; vocabularies(): AnyVocabSource[]; -}; +} export interface WritableConfig { setDefaultLatLng(val: Point2d): void; @@ -164,7 +166,8 @@ export interface WritableConfig { setShowDatasetsPanel(val: boolean): void; setShowDirectoryPanel(val: boolean): void; setShowSearchPanel(val: boolean): void; -}; + setDefaultPanel(val: SidebarId): void; +} export interface ConfigSchema { // An identifier string @@ -185,7 +188,7 @@ export interface DialogueSize { width?: string; height?: string; descriptionRatio?: number; -}; +} export type InitiativeRenderFunction = (initiative: Initiative, model: DataServices) => string; @@ -232,6 +235,7 @@ export class ConfigData { showDatasetsPanel: boolean = true; showDirectoryPanel: boolean = true; showSearchPanel: boolean = true; + defaultPanel: SidebarId = 'directory'; sidebarButtonColour: string = '#39cccc'; tileUrl?: string; timestamp: string = '2000-01-01T00:00:00.000Z'; @@ -244,12 +248,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. @@ -403,6 +407,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, '+ @@ -414,7 +426,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', }), -}; +} @@ -714,7 +726,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, @@ -728,18 +740,26 @@ 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, }, + defaultPanel: { + id: "defaultPanel", + descr: "Defines which panel opens by default.", + defaultDescr: "If unset, the default is 'directory'", + getter: "getDefaultPanel", + setter: "setDefaultPanel", + type: types.sidebarId, + }, sidebarButtonColour: { id: "sidebarButtonColour", descr: 'Set the css background-colour attribute for the open sidebar button. Defaults to teal', @@ -1010,6 +1030,9 @@ ${def.descr} getShowSearchPanel(): boolean { return this.data.showSearchPanel; } + getDefaultPanel(): SidebarId { + return this.data.defaultPanel; + } getSidebarButtonColour(): string { return this.data.sidebarButtonColour; } @@ -1095,8 +1118,9 @@ ${def.descr} setShowSearchPanel(val: boolean): void { this.data.showSearchPanel = val; } + setDefaultPanel(val: SidebarId): void { + this.data.defaultPanel = val; + } -// [id: string]: Getter | Setter; -}; - - + // [id: string]: Getter | Setter; +} diff --git a/src/map-app/app/presenter/sidebar.ts b/src/map-app/app/presenter/sidebar.ts index 461b8792..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,16 +37,13 @@ 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(); + this.view = new SidebarView( + this, + mapui.dataServices.getSidebarButtonColour() + ); this._eventbusRegister(); - this.createSidebars(); - this.changeSidebar(); - } - - createSidebars() { - this.children = {}; - if(this.showingDirectory()) this.children.directory = new DirectorySidebarPresenter(this); @@ -45,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() { diff --git a/test/configs/typical/config.json b/test/configs/typical/config.json index 6de7f67d..cc90d608 100644 --- a/test/configs/typical/config.json +++ b/test/configs/typical/config.json @@ -13,5 +13,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 41d0c655..431657fe 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); }); }); });