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

MM-231 Support config for which tab in sidebar is selected on map startup #236

Merged
merged 8 commits into from
Apr 18, 2024
Merged
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
**
18 changes: 15 additions & 3 deletions CONFIG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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



Expand All @@ -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



Expand All @@ -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



Expand Down
54 changes: 39 additions & 15 deletions src/map-app/app/model/config-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import type {

import { Initiative, InitiativeObj } from './initiative';
import { isIso6391Code, Iso6391Code } from '../../localisations';
import { SidebarId } from '../presenter/sidebar';

class TypeDef<T> {
constructor(params: {
Expand All @@ -54,7 +55,7 @@ class TypeDef<T> {
stringDescr?: string;
// A string-parsing function
parseString?: (val: string) => T;
};
}


export interface VocabSource {
Expand Down Expand Up @@ -84,7 +85,7 @@ export interface DataSource {
id: string;
type: string;
label: string;
};
}

export interface HostSparqlDataSource extends DataSource {
type: 'hostSparql';
Expand Down Expand Up @@ -133,6 +134,7 @@ export interface ReadableConfig {
getShowDatasetsPanel(): boolean;
getShowDirectoryPanel(): boolean;
getShowSearchPanel(): boolean;
getDefaultPanel(): SidebarId;
getSidebarButtonColour(): string;
getSoftwareGitCommit(): string;
getSoftwareTimestamp(): string;
Expand All @@ -142,7 +144,7 @@ export interface ReadableConfig {
htmlTitle(): string;
logo(): string | undefined;
vocabularies(): AnyVocabSource[];
};
}

export interface WritableConfig {
setDefaultLatLng(val: Point2d): void;
Expand All @@ -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<T> {
// An identifier string
Expand All @@ -185,7 +188,7 @@ export interface DialogueSize {
width?: string;
height?: string;
descriptionRatio?: number;
};
}

export type InitiativeRenderFunction =
(initiative: Initiative, model: DataServices) => string;
Expand Down Expand Up @@ -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';
Expand All @@ -244,12 +248,12 @@ export class ConfigData {
constructor(params: Partial<ConfigData> = {}) {
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<ConfigData[K]> };
export type ConfigSchemas = { [K in keyof ConfigData]: ConfigSchema<ConfigData[K]> }


// Validates/normalises a language code.
Expand Down Expand Up @@ -403,6 +407,14 @@ const types = {
name: '{InitiativeRenderFunction}',
descr: 'A function which accepts an Initiative instance and returns an HTML string',
}),
sidebarId: new TypeDef<SidebarId>({
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<AnyVocabSource[]>({
name: '{AnyVocabSource[]}',
descr: 'An array of vocab source definitions, defining a SPARQL endpoint URL, '+
Expand All @@ -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',
}),
};
}



Expand Down Expand Up @@ -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,
Expand All @@ -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',
Expand Down Expand Up @@ -1010,6 +1030,9 @@ ${def.descr}
getShowSearchPanel(): boolean {
return this.data.showSearchPanel;
}
getDefaultPanel(): SidebarId {
return this.data.defaultPanel;
}
getSidebarButtonColour(): string {
return this.data.sidebarButtonColour;
}
Expand Down Expand Up @@ -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;
}
93 changes: 57 additions & 36 deletions src/map-app/app/presenter/sidebar.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,49 @@
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<BaseSidebarPresenter> = {};
private sidebarName?: string;
private readonly children = new SidebarPanels();

private sidebarName?: SidebarId;

constructor(readonly mapui: MapUI) {
super();
this.showDirectoryPanel = mapui.config.getShowDirectoryPanel();
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);

Expand All @@ -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() {
Expand Down
4 changes: 3 additions & 1 deletion test/configs/typical/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,7 @@
"disableClusteringAtZoom": 10,
"maxZoomOnGroup":12,
"maxZoomOnOne": 14,
"maxZoomOnSearch": 12
"maxZoomOnSearch": 12,
"defaultPanel": "about",
"defaultOpenSidebar": true
}
4 changes: 2 additions & 2 deletions test/test-model-config.js
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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 () {
Expand Down Expand Up @@ -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);
});
});
});
Loading