Skip to content

Commit

Permalink
app/{model/config-schema.ts,presenter/sidebar.ts} - make defaultPanel…
Browse files Browse the repository at this point in the history
… 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)
  • Loading branch information
Nick Stokoe committed Mar 20, 2024
1 parent 928fd8c commit 21df6c7
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 44 deletions.
25 changes: 17 additions & 8 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 Down Expand Up @@ -133,7 +134,7 @@ export interface ReadableConfig {
getShowDatasetsPanel(): boolean;
getShowDirectoryPanel(): boolean;
getShowSearchPanel(): boolean;
getDefaultPanel(): string;
getDefaultPanel(): SidebarId;
getSidebarButtonColour(): string;
getSoftwareGitCommit(): string;
getSoftwareTimestamp(): string;
Expand Down Expand Up @@ -165,7 +166,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<T> {
Expand Down Expand Up @@ -234,7 +235,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';
Expand Down Expand Up @@ -406,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 Down Expand Up @@ -745,11 +754,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",
Expand Down Expand Up @@ -1021,7 +1030,7 @@ ${def.descr}
getShowSearchPanel(): boolean {
return this.data.showSearchPanel;
}
getDefaultPanel(): string {
getDefaultPanel(): SidebarId {
return this.data.defaultPanel;
}
getSidebarButtonColour(): string {
Expand Down Expand Up @@ -1109,7 +1118,7 @@ ${def.descr}
setShowSearchPanel(val: boolean): void {
this.data.showSearchPanel = val;
}
setDefaultPanel(val: string): void {
setDefaultPanel(val: SidebarId): void {
this.data.defaultPanel = val;
}

Expand Down
89 changes: 53 additions & 36 deletions src/map-app/app/presenter/sidebar.ts
Original file line number Diff line number Diff line change
@@ -1,43 +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();
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);

Expand All @@ -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() {
Expand Down

0 comments on commit 21df6c7

Please sign in to comment.