diff --git a/ui/datalist.ts b/ui/datalist.ts new file mode 100644 index 0000000..845e91a --- /dev/null +++ b/ui/datalist.ts @@ -0,0 +1,62 @@ +/** + * Copyright 2013-2021 GenieACS Inc. + * + * This file is part of GenieACS. + * + * GenieACS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * GenieACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with GenieACS. If not, see . + */ + +import m, { ClosureComponent, Component, Vnode } from "mithril"; + +const elements: Map = new Map(); + +// Source: https://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/ +function hash(str: string): number { + let res = 0; + for (let i = 0; i < str.length; ++i) { + const c = str.charCodeAt(i); + res = (res << 5) - res + c; + res |= 0; + } + return res; +} + +export function getDatalistId(options: string[]): string { + const id = "datalist" + options.reduce((acc, cur) => acc ^ hash(cur), 0); + if (!elements.has(id)) { + const n = m( + "datalist", + { id }, + options.map((o) => m("option", { value: o })) + ); + elements.set(id, n); + } + return id; +} + +const component: ClosureComponent = (): Component => { + return { + view: () => { + return [...elements.values()]; + }, + onupdate: () => { + for (const id of elements.keys()) { + const used = document.querySelector(`[list='${id}']`); + if (!used) elements.delete(id); + } + }, + }; +}; + +export default component; diff --git a/ui/drawer-component.ts b/ui/drawer-component.ts index e4bf652..03bd9ff 100644 --- a/ui/drawer-component.ts +++ b/ui/drawer-component.ts @@ -121,12 +121,15 @@ function renderStagingDownload(task: StageTask): Children { if (productClass) productClass = decodeURIComponent(productClass); const typesList = [ - "", - "1 Firmware Upgrade Image", - "2 Web Content", - "3 Vendor Configuration File", - "4 Tone File", - "5 Ringer File", + ...new Set([ + "", + "1 Firmware Upgrade Image", + "2 Web Content", + "3 Vendor Configuration File", + "4 Tone File", + "5 Ringer File", + ...files.value.map((f) => f["metadata.fileType"]).filter((f) => f), + ]), ].map((t) => m( "option", diff --git a/ui/files-page.ts b/ui/files-page.ts index e1fde75..bfa3762 100644 --- a/ui/files-page.ts +++ b/ui/files-page.ts @@ -45,7 +45,6 @@ const attributes: { { id: "metadata.fileType", label: "Type", - type: "combo", options: [ "1 Firmware Upgrade Image", "2 Web Content", diff --git a/ui/layout.ts b/ui/layout.ts index e3e1317..53fcbed 100644 --- a/ui/layout.ts +++ b/ui/layout.ts @@ -24,6 +24,7 @@ import userMenu from "./user-menu"; import adminMenu from "./admin-menu"; import * as overlay from "./overlay"; import { version as VERSION } from "../package.json"; +import datalist from "./datalist"; const adminPages = [ "presets", @@ -69,6 +70,7 @@ const component: ClosureComponent = (): Component => { ]) ), overlay.render(), + m(datalist), ]; }, }; diff --git a/ui/put-form-component.ts b/ui/put-form-component.ts index 514b973..0f0c786 100644 --- a/ui/put-form-component.ts +++ b/ui/put-form-component.ts @@ -20,6 +20,7 @@ import { VnodeDOM, ClosureComponent, Component, Children } from "mithril"; import { m } from "./components"; import codeEditorComponent from "./code-editor-component"; +import { getDatalistId } from "./datalist"; const singular = { presets: "preset", @@ -151,9 +152,14 @@ function createField(current, attr, focus): Children { }); } + let datalist: string = null; + if (attr.options) datalist = getDatalistId(attr.options); + return m("input", { type: attr.type === "password" ? "password" : "text", name: attr.id, + list: datalist, + autocomplete: datalist ? "off" : null, disabled: attr.id === "_id" && !current.isNew, value: current.object[attr.id], oncreate: focus