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