/** @jsx m */ /** @jsxFrag m.fragment */ import { WeatherApi, SettingsSpec, SettingField } from "./types.js"; import { checkForError } from "./api.js"; import { MockWeatherApi } from "./mock_api.js"; import { addTest, TestCase } from "./third_party/test.js"; export class SettingsComponent { private api: WeatherApi; private tenantId: string; private account: string; private spec: SettingsSpec | null; private values: Record; private loading: boolean; private saving: boolean; private message: string; private messageType: "success" | "error" | ""; constructor(api: WeatherApi, tenantId: string, account: string) { this.api = api; this.tenantId = tenantId; this.account = account; this.spec = null; this.values = {}; this.loading = true; this.saving = false; this.message = ""; this.messageType = ""; } oninit(vnode: MithrilVnode): void { this.loadSettings(); } private loadSettings(): void { this.loading = true; this.api.getSettings(this.tenantId, this.account).then((resp) => { if (!checkForError(resp)) { this.message = resp.error || "unknown error"; this.messageType = "error"; } else if (resp.data) { this.spec = resp.data.spec; this.values = { ...resp.data.values }; } this.loading = false; m.redraw(); }); } private onFieldChange(key: string, value: any): void { this.values[key] = value; } private onSave(): void { this.saving = true; this.message = ""; this.api.saveSettings(this.tenantId, this.account, this.values).then((resp) => { if (!checkForError(resp)) { this.message = resp.error || "save failed"; this.messageType = "error"; } else { this.message = "Settings saved"; this.messageType = "success"; } this.saving = false; m.redraw(); }); } view(vnode: MithrilVnode): MithrilRawElement { if (this.loading) { return
Loading...
; } if (!this.spec) { return
Failed to load settings
; } const self = this; const fields = this.spec.fields.map((field) => { return self.renderField(field); }); let messageEl: MithrilRawElement | null = null; if (this.message) { messageEl =
{this.message}
; } return (

Settings

{fields} {messageEl}
); } private renderField(field: SettingField): MithrilRawElement { switch (field.type) { case "radio": return this.renderRadioField(field); case "number": return this.renderNumberField(field); case "string": return this.renderStringField(field); } } private renderRadioField(field: SettingField): MithrilRawElement { const self = this; const options = (field.options || []).map((opt) => { return ( ); }); return (
{field.label} {options}
); } private renderNumberField(field: SettingField): MithrilRawElement { const self = this; return (
{ const numVal = parseFloat(e.target.value); if (!isNaN(numVal)) { self.onFieldChange(field.key, numVal); } }} />
); } private renderStringField(field: SettingField): MithrilRawElement { const self = this; return (
self.onFieldChange(field.key, e.target.value)} />
); } } addTest("SettingsComponent view renders loading state", (t: TestCase) => { const api = new MockWeatherApi(); const component = new SettingsComponent(api, "t1", "jwt"); const result = component.view({} as MithrilVnode); t.defined(result); }); addTest("SettingsComponent view renders with spec and values", (t: TestCase) => { const api = new MockWeatherApi(undefined, { data: { spec: { fields: [ { key: "tempUnit", label: "Temperature Unit", type: "radio", options: [{ label: "F", value: "F" }, { label: "C", value: "C" }] }, { key: "coldThreshold", label: "Cold", type: "number" }, { key: "sourceZip", label: "Zip", type: "string" }, ], }, values: { tempUnit: "F", coldThreshold: 45, sourceZip: "94086" }, }, }); const component = new SettingsComponent(api, "t1", "jwt"); component["spec"] = api["settingsResponse"].data!.spec; component["values"] = { ...api["settingsResponse"].data!.values }; component["loading"] = false; const result = component.view({} as MithrilVnode); t.defined(result); }); addTest("SettingsComponent view renders error state", (t: TestCase) => { const api = new MockWeatherApi(); const component = new SettingsComponent(api, "t1", "jwt"); component["loading"] = false; component["message"] = "something went wrong"; component["messageType"] = "error"; const result = component.view({} as MithrilVnode); t.defined(result); }); addTest("SettingsComponent onFieldChange updates values", (t: TestCase) => { const api = new MockWeatherApi(); const component = new SettingsComponent(api, "t1", "jwt"); component["values"] = { tempUnit: "F" }; component["onFieldChange"]("tempUnit", "C"); t.equals("C", component["values"]["tempUnit"]); });