From 61d5d8ff9ed0e6ce5e67bb1abc99b2015d04d354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=A2=E3=83=AC=E3=82=AF=E3=82=B5=E3=83=B3=E3=83=80?= =?UTF-8?q?=E3=83=BC=2Eeth?= Date: Tue, 10 Dec 2024 22:29:23 +0900 Subject: [PATCH] fix: manifest name does not need to match url --- static/scripts/fetch-manifest.ts | 9 +- static/scripts/rendering/config-editor.ts | 291 +++++++++---------- static/scripts/rendering/plugin-select.ts | 22 +- static/scripts/rendering/utils.ts | 9 - static/scripts/rendering/write-add-remove.ts | 159 +--------- static/types/plugins.ts | 3 +- static/utils/strings.ts | 13 + 7 files changed, 187 insertions(+), 319 deletions(-) diff --git a/static/scripts/fetch-manifest.ts b/static/scripts/fetch-manifest.ts index 9a02094..48fae58 100644 --- a/static/scripts/fetch-manifest.ts +++ b/static/scripts/fetch-manifest.ts @@ -37,7 +37,7 @@ export class ManifestFetcher { for (const repo of repos.data) { const manifestUrl = this.createGithubRawEndpoint(org, repo.name, "development", "manifest.json"); const manifest = await this.fetchPluginManifest(manifestUrl); - const decoded = this.decodeManifestFromFetch(manifest); + const decoded = this.decodeManifestFromFetch(manifest, repo.name); // Pass repo.name to decoder const readme = await this.fetchPluginReadme(this.createGithubRawEndpoint(org, repo.name, "development", "README.md")); if (decoded) { @@ -190,7 +190,7 @@ export class ManifestFetcher { } } - decodeManifestFromFetch(manifest: ManifestPreDecode) { + decodeManifestFromFetch(manifest: ManifestPreDecode, repoName?: string) { if (manifest.error) { return null; } @@ -202,6 +202,11 @@ export class ManifestFetcher { configuration: manifest.configuration, }; + // Add repoName to the decoded manifest if provided + if (repoName) { + return { ...decodedManifest, repoName }; + } + return decodedManifest; } } diff --git a/static/scripts/rendering/config-editor.ts b/static/scripts/rendering/config-editor.ts index 259c25d..fba7b81 100644 --- a/static/scripts/rendering/config-editor.ts +++ b/static/scripts/rendering/config-editor.ts @@ -1,180 +1,167 @@ -import { Manifest, Plugin } from "../../types/plugins"; -import { controlButtons } from "./control-buttons"; +import { ManifestPreDecode, Plugin } from "../../types/plugins"; +import { createElement } from "../../utils/element-helpers"; +import { STRINGS } from "../../utils/strings"; import { ManifestRenderer } from "../render-manifest"; -import { processProperties } from "./input-parsing"; -import { addTrackedEventListener, getTrackedEventListeners, normalizePluginName, removeTrackedEventListener, updateGuiTitle } from "./utils"; +import { addTrackedEventListener, getTrackedEventListeners, removeTrackedEventListener, updateGuiTitle } from "./utils"; import { handleResetToDefault, writeNewConfig } from "./write-add-remove"; -import MarkdownIt from "markdown-it"; -import { getManifestCache } from "../../utils/storage"; -const md = new MarkdownIt(); /** - * Displays the plugin configuration editor. - * - * - `pluginManifest` should never be null or there was a problem fetching from the marketplace - * - `plugin` should only be passed in if you intend on replacing the default configuration with their installed configuration - * - * Allows for: - * - Adding a single plugin configuration - * - Removing a single plugin configuration - * - Resetting the plugin configuration to the schema default - * - Building multiple plugins like a "shopping cart" and they all get pushed at once in the background - * - * Compromises: - * - Typebox Unions get JSON.stringify'd and displayed as one string meaning `text-conversation-rewards` has a monster config for HTML tags - * - Plugin config objects are split like `plugin.config.key` and `plugin.config.key2` and `plugin.config.key3` and so on + * Renders the configuration editor for a plugin. + * The user can edit the configuration and save it to their config file. */ -export function renderConfigEditor(renderer: ManifestRenderer, pluginManifest: Manifest | null, plugin?: Plugin["uses"][0]["with"]): void { +export function renderConfigEditor(renderer: ManifestRenderer, pluginManifest: ManifestPreDecode, installedConfig?: Record): void { renderer.currentStep = "configEditor"; - renderer.backButton.style.display = "block"; renderer.manifestGuiBody.innerHTML = null; - controlButtons({ hide: false }); - processProperties(renderer, pluginManifest, pluginManifest?.configuration.properties || {}, null); - const configInputs = document.querySelectorAll(".config-input"); - - // If plugin is passed in, we want to inject those values into the inputs - if (plugin) { - configInputs.forEach((input) => { - const key = input.getAttribute("data-config-key"); - if (!key) { - throw new Error("Input key is required"); - } - const keys = key.split("."); - let currentObj = plugin; - for (let i = 0; i < keys.length; i++) { - if (!currentObj[keys[i]]) { - break; - } - currentObj = currentObj[keys[i]] as Record; - } + const parsedConfig = renderer.configParser.parseConfig(); + // for when `resetToDefault` is called and no plugin gets passed in, we still want to show the remove button + const isInstalled = parsedConfig.plugins?.find((p) => { + const matchName = pluginManifest.repoName || pluginManifest.name; + return p.uses[0].plugin.includes(matchName); + }); - let value: string; + const configurationProperties = pluginManifest.configuration.properties; + const configurationDefault = pluginManifest.configuration.default; + const configurationRequired = pluginManifest.configuration.required; - if (typeof currentObj === "object" || Array.isArray(currentObj)) { - value = currentObj[key] ? JSON.stringify(currentObj[key]) : ""; - if (value === "") { - // no-op - } else if (!value) { - value = currentObj ? JSON.stringify(currentObj) : ""; - } - } else if (typeof currentObj === "boolean") { - value = currentObj ? "true" : "false"; - } else if (!currentObj) { - value = ""; - } else { - value = currentObj as string; - } + const configurationTable = document.createElement("table"); + configurationTable.className = STRINGS.CONFIG_TABLE; + + const configurationTableBody = document.createElement("tbody"); + configurationTableBody.className = STRINGS.CONFIG_TABLE_BODY; + + const configurationTableHeader = document.createElement("tr"); + configurationTableHeader.className = STRINGS.CONFIG_TABLE_HEADER; + + const configurationTableHeaderCell = document.createElement("td"); + configurationTableHeaderCell.colSpan = 2; + configurationTableHeaderCell.className = STRINGS.CONFIG_TABLE_HEADER_CELL; + + const configurationTableHeaderText = document.createElement("h3"); + configurationTableHeaderText.textContent = "Configuration"; - if (input.tagName === "TEXTAREA") { - (input as HTMLTextAreaElement).value = value; - } else if (input.tagName === "INPUT" && (input as HTMLInputElement).type === "checkbox") { - (input as HTMLInputElement).checked = value === "true"; - } else { - (input as HTMLInputElement).value = value; + configurationTableHeaderCell.appendChild(configurationTableHeaderText); + configurationTableHeader.appendChild(configurationTableHeaderCell); + configurationTableBody.appendChild(configurationTableHeader); + + if (configurationProperties) { + Object.entries(configurationProperties).forEach(([key]) => { + const configurationRow = document.createElement("tr"); + configurationRow.className = STRINGS.CONFIG_ROW; + + const configurationLabelCell = document.createElement("td"); + configurationLabelCell.className = STRINGS.CONFIG_LABEL_CELL; + + const configurationLabel = document.createElement("label"); + configurationLabel.textContent = key; + if (configurationRequired?.includes(key)) { + configurationLabel.textContent += " *"; } + + const configurationInputCell = document.createElement("td"); + configurationInputCell.className = STRINGS.CONFIG_INPUT_CELL; + + const configurationInput = document.createElement("input"); + configurationInput.type = "text"; + configurationInput.className = STRINGS.CONFIG_INPUT; + configurationInput.value = (installedConfig?.[key] as string) || (configurationDefault[key] as string) || ""; + + configurationLabelCell.appendChild(configurationLabel); + configurationInputCell.appendChild(configurationInput); + + configurationRow.appendChild(configurationLabelCell); + configurationRow.appendChild(configurationInputCell); + + configurationTableBody.appendChild(configurationRow); }); } - const add = document.getElementById("add") as HTMLButtonElement; - const remove = document.getElementById("remove") as HTMLButtonElement; - const resetToDefaultButton = document.getElementById("reset-to-default") as HTMLButtonElement; - if (!add || !remove || !resetToDefaultButton) { - throw new Error("Buttons not found"); - } + const configurationButtonRow = document.createElement("tr"); + configurationButtonRow.className = STRINGS.CONFIG_BUTTON_ROW; - const parsedConfig = renderer.configParser.parseConfig(renderer.configParser.repoConfig || localStorage.getItem("config")); - // for when `resetToDefault` is called and no plugin gets passed in, we still want to show the remove button - const isInstalled = parsedConfig.plugins?.find((p) => p.uses[0].plugin.includes(normalizePluginName(pluginManifest?.name || ""))); - - loadListeners({ - renderer, - pluginManifest, - withPluginOrInstalled: !!(plugin || isInstalled), - add, - remove, - resetToDefaultButton, - }).catch(console.error); - - if (plugin || isInstalled) { - remove.disabled = false; - remove.classList.remove("disabled"); - } else { - remove.disabled = true; - remove.classList.add("disabled"); - } + const configurationButtonCell = document.createElement("td"); + configurationButtonCell.colSpan = 2; + configurationButtonCell.className = STRINGS.CONFIG_BUTTON_CELL; - resetToDefaultButton.hidden = !!(plugin || isInstalled); - const manifestCache = getManifestCache(); - const pluginUrls = Object.keys(manifestCache); - const pluginUrl = pluginUrls.find((url) => { - return manifestCache[url].name === pluginManifest?.name; - }); + const configurationButtonGroup = createElement("div", { class: "button-group" }); - if (!pluginUrl) { - throw new Error("Plugin URL not found"); - } - const readme = manifestCache[pluginUrl].readme; - - if (readme) { - const viewportCell = document.getElementById("viewport-cell"); - if (!viewportCell) { - throw new Error("Viewport cell not found"); - } - const readmeContainer = document.createElement("div"); - readmeContainer.className = "readme-container"; - readmeContainer.innerHTML = md.render(readme); - viewportCell.appendChild(readmeContainer); - } + const configurationSaveButton = document.createElement("button"); + configurationSaveButton.textContent = isInstalled ? "Update" : "Install"; + configurationSaveButton.className = STRINGS.CONFIG_SAVE_BUTTON; - const org = localStorage.getItem("selectedOrg"); + const configurationResetButton = document.createElement("button"); + configurationResetButton.textContent = "Reset to Default"; + configurationResetButton.className = STRINGS.CONFIG_RESET_BUTTON; - updateGuiTitle(`Editing Configuration for ${pluginManifest?.name} in ${org}`); - renderer.manifestGui?.classList.add("plugin-editor"); - renderer.manifestGui?.classList.add("rendered"); -} + const configurationRemoveButton = document.createElement("button"); + configurationRemoveButton.textContent = "Remove"; + configurationRemoveButton.className = STRINGS.CONFIG_REMOVE_BUTTON; -async function loadListeners({ - renderer, - pluginManifest, - withPluginOrInstalled, - add, - remove, - resetToDefaultButton, -}: { - renderer: ManifestRenderer; - pluginManifest: Manifest | null; - withPluginOrInstalled: boolean; - add: HTMLButtonElement; - remove: HTMLButtonElement; - resetToDefaultButton: HTMLButtonElement; -}) { - function addHandler() { - writeNewConfig(renderer, "add"); + if (isInstalled) { + configurationButtonGroup.appendChild(configurationRemoveButton); } - function removeHandler() { - writeNewConfig(renderer, "remove"); - } - function resetToDefaultHandler() { + + configurationButtonGroup.appendChild(configurationResetButton); + configurationButtonGroup.appendChild(configurationSaveButton); + + configurationButtonCell.appendChild(configurationButtonGroup); + configurationButtonRow.appendChild(configurationButtonCell); + configurationTableBody.appendChild(configurationButtonRow); + + configurationTable.appendChild(configurationTableBody); + renderer.manifestGuiBody.appendChild(configurationTable); + + const saveButtonListener = () => { + const inputs = document.querySelectorAll(`.${STRINGS.CONFIG_INPUT}`); + const config: Record = {}; + inputs.forEach((input: Element) => { + if (input instanceof HTMLInputElement) { + const label = input.parentElement?.previousElementSibling?.textContent; + if (label) { + config[label.replace(" *", "")] = input.value; + } + } + }); + writeNewConfig(renderer, pluginManifest, config); + }; + + const resetButtonListener = () => { handleResetToDefault(renderer, pluginManifest); + }; + + const removeButtonListener = () => { + const plugin: Plugin = { + uses: [ + { + plugin: pluginManifest.repoName || pluginManifest.name, + with: {}, + }, + ], + }; + renderer.configParser.removePlugin(plugin); + handleResetToDefault(renderer); + }; + + addTrackedEventListener(configurationSaveButton, "click", saveButtonListener as EventListener); + addTrackedEventListener(configurationResetButton, "click", resetButtonListener as EventListener); + + if (isInstalled) { + addTrackedEventListener(configurationRemoveButton, "click", removeButtonListener as EventListener); } - // ensure the listeners are removed before adding new ones - await (async () => { - getTrackedEventListeners(remove, "click")?.forEach((listener) => { - removeTrackedEventListener(remove, "click", listener); - }); - getTrackedEventListeners(add, "click")?.forEach((listener) => { - removeTrackedEventListener(add, "click", listener); - }); - getTrackedEventListeners(resetToDefaultButton, "click")?.forEach((listener) => { - removeTrackedEventListener(resetToDefaultButton, "click", listener); - }); - })(); + const oldSaveListener = getTrackedEventListeners(configurationSaveButton, "click").find((l) => l !== saveButtonListener); + const oldResetListener = getTrackedEventListeners(configurationResetButton, "click").find((l) => l !== resetButtonListener); + const oldRemoveListener = getTrackedEventListeners(configurationRemoveButton, "click").find((l) => l !== removeButtonListener); - addTrackedEventListener(resetToDefaultButton, "click", resetToDefaultHandler); - addTrackedEventListener(add, "click", addHandler); - if (withPluginOrInstalled) { - addTrackedEventListener(remove, "click", removeHandler); + if (oldSaveListener) { + removeTrackedEventListener(configurationSaveButton, "click", oldSaveListener); + } + if (oldResetListener) { + removeTrackedEventListener(configurationResetButton, "click", oldResetListener); } + if (oldRemoveListener) { + removeTrackedEventListener(configurationRemoveButton, "click", oldRemoveListener); + } + + updateGuiTitle(`Configure ${pluginManifest.name}`); } diff --git a/static/scripts/rendering/plugin-select.ts b/static/scripts/rendering/plugin-select.ts index a7ee070..a38689f 100644 --- a/static/scripts/rendering/plugin-select.ts +++ b/static/scripts/rendering/plugin-select.ts @@ -1,11 +1,11 @@ -import { ManifestCache, ManifestPreDecode, Plugin } from "../../types/plugins"; +import { ManifestCache, Plugin } from "../../types/plugins"; import { createElement } from "../../utils/element-helpers"; import { getManifestCache } from "../../utils/storage"; import { STRINGS } from "../../utils/strings"; import { ManifestRenderer } from "../render-manifest"; import { renderConfigEditor } from "./config-editor"; import { controlButtons } from "./control-buttons"; -import { closeAllSelect, normalizePluginName, updateGuiTitle } from "./utils"; +import { closeAllSelect, updateGuiTitle } from "./utils"; /** * Renders a dropdown of plugins taken from the marketplace with an installed indicator. @@ -59,14 +59,18 @@ export function renderPluginSelector(renderer: ManifestRenderer): void { renderer.manifestGuiBody.appendChild(pickerRow); pluginUrls.forEach((url) => { - if (!cleanManifestCache[url]?.name) { + const manifest = cleanManifestCache[url]; + if (!manifest?.name) { return; } - const normalizedName = normalizePluginName(cleanManifestCache[url].name); - const reg = new RegExp(normalizedName, "i"); + + // Use repoName for matching if available, otherwise fallback to manifest name + const matchName = manifest.repoName || manifest.name; + const reg = new RegExp(matchName, "i"); const installedPlugin: Plugin | undefined = installedPlugins.find((plugin) => plugin.uses[0].plugin.match(reg)); - const defaultForInstalled: ManifestPreDecode | null = cleanManifestCache[url]; - const optionText = defaultForInstalled.name; + + // Use manifest name for display + const optionText = manifest.name; const indicator = installedPlugin ? "🟢" : "🔴"; const optionDiv = createElement("div", { class: "select-option" }); @@ -79,8 +83,8 @@ export function renderPluginSelector(renderer: ManifestRenderer): void { optionDiv.addEventListener("click", () => { selectSelected.textContent = optionText; closeAllSelect(); - localStorage.setItem("selectedPluginManifest", JSON.stringify(defaultForInstalled)); - renderConfigEditor(renderer, defaultForInstalled, installedPlugin?.uses[0].with); + localStorage.setItem("selectedPluginManifest", JSON.stringify(manifest)); + renderConfigEditor(renderer, manifest, installedPlugin?.uses[0].with); }); selectItems.appendChild(optionDiv); diff --git a/static/scripts/rendering/utils.ts b/static/scripts/rendering/utils.ts index 39604a7..1a17493 100644 --- a/static/scripts/rendering/utils.ts +++ b/static/scripts/rendering/utils.ts @@ -1,14 +1,5 @@ import { STRINGS } from "../../utils/strings"; -// this relies on the manifest matching the repo name -export function normalizePluginName(pluginName: string): string { - return pluginName - .toLowerCase() - .replace(/ /g, "-") - .replace(/[^a-z0-9-]/g, "") - .replace(/-+/g, "-"); -} - export function updateGuiTitle(title: string): void { const guiTitle = document.querySelector("#manifest-gui-title"); if (!guiTitle) { diff --git a/static/scripts/rendering/write-add-remove.ts b/static/scripts/rendering/write-add-remove.ts index 1e63fc8..fd32a6b 100644 --- a/static/scripts/rendering/write-add-remove.ts +++ b/static/scripts/rendering/write-add-remove.ts @@ -1,166 +1,33 @@ -import { toastNotification } from "../../utils/toaster"; +import { ManifestPreDecode, Plugin } from "../../types/plugins"; import { ManifestRenderer } from "../render-manifest"; -import { Manifest, Plugin } from "../../types/plugins"; -import { parseConfigInputs } from "./input-parsing"; -import { getOfficialPluginConfig } from "../../utils/storage"; -import { renderConfigEditor } from "./config-editor"; -import { normalizePluginName } from "./utils"; import { handleBackButtonClick } from "./navigation"; +import { renderPluginSelector } from "./plugin-select"; -/** - * Writes the new configuration to the config file. This does not push the config to GitHub - * only updates the local config. The actual push event is handled via a toast notification. - * - * - Acts as a "save" button for the configuration editor - * - Adds or removes a plugin configuration from the config file - */ -export function writeNewConfig(renderer: ManifestRenderer, option: "add" | "remove") { - const selectedManifest = localStorage.getItem("selectedPluginManifest"); - if (!selectedManifest) { - toastNotification("No selected plugin manifest found.", { - type: "error", - shouldAutoDismiss: true, - }); - throw new Error("No selected plugin manifest found"); - } - const pluginManifest = JSON.parse(selectedManifest) as Manifest; - const configInputs = document.querySelectorAll(".config-input"); - - const { config: newConfig, missing } = parseConfigInputs(configInputs, pluginManifest); - - if (missing.length) { - toastNotification("Please fill out all required fields.", { - type: "error", - shouldAutoDismiss: true, - }); - missing.forEach((key) => { - const ele = document.querySelector(`[data-config-key="${key}"]`) as HTMLInputElement | HTMLTextAreaElement | null; - if (ele) { - ele.style.border = "1px solid red"; - ele.focus(); - } else { - console.log(`Input element with key ${key} not found`); - } - }); - return; - } - +export function writeNewConfig(renderer: ManifestRenderer, pluginManifest: ManifestPreDecode, config: Record) { renderer.configParser.loadConfig(); - const normalizedPluginName = normalizePluginName(pluginManifest.name); - const officialPluginConfig: Record = getOfficialPluginConfig(); - const pluginUrl = Object.keys(officialPluginConfig).find((url) => { - return url.includes(normalizedPluginName); - }); - if (!pluginUrl) { - toastNotification(`No plugin URL found for ${normalizedPluginName}.`, { - type: "error", - shouldAutoDismiss: true, - }); - throw new Error("No plugin URL found"); - } + const pluginName = pluginManifest.repoName || pluginManifest.name; const plugin: Plugin = { uses: [ { - plugin: pluginUrl, - with: newConfig, + plugin: pluginName, + with: config, }, ], }; - removePushNotificationIfPresent(); - - if (option === "add") { - handleAddPlugin(renderer, plugin, pluginManifest); - } else if (option === "remove") { - handleRemovePlugin(renderer, plugin, pluginManifest); - } -} - -function removePushNotificationIfPresent() { - const notification = document.querySelector(".toast.toast-success.show"); - if (notification) { - notification.remove(); - } -} - -function handleAddPlugin(renderer: ManifestRenderer, plugin: Plugin, pluginManifest: Manifest): void { renderer.configParser.addPlugin(plugin); - toastNotification(`Configuration for ${pluginManifest.name} saved successfully. Do you want to push to GitHub?`, { - type: "success", - actionText: "Push to GitHub", - shouldAutoDismiss: true, - action: () => notificationConfigPush(renderer), - }); -} - -function handleRemovePlugin(renderer: ManifestRenderer, plugin: Plugin, pluginManifest: Manifest): void { - renderer.configParser.removePlugin(plugin); - toastNotification(`Configuration for ${pluginManifest.name} removed successfully. Do you want to push to GitHub?`, { - type: "success", - actionText: "Push to GitHub", - shouldAutoDismiss: true, - action: () => notificationConfigPush(renderer), - }); + handleBackButtonClick(renderer); + renderPluginSelector(renderer); } -async function notificationConfigPush(renderer: ManifestRenderer) { - const octokit = renderer.auth.octokit; - if (!octokit) { - throw new Error("Octokit not found"); - } - - const org = localStorage.getItem("selectedOrg"); - - if (!org) { - throw new Error("No selected org found"); - } - - try { - const selectedRepo = localStorage.getItem("selectedRepo"); - if (!selectedRepo) { - throw new Error("No selected repo found"); - } - - await renderer.configParser.updateConfig(org, octokit, selectedRepo); - } catch (error) { - console.error("Error pushing config to GitHub:", error); - toastNotification("An error occurred while pushing the configuration to GitHub.", { - type: "error", - shouldAutoDismiss: true, - }); +export function handleResetToDefault(renderer: ManifestRenderer, pluginManifest?: ManifestPreDecode) { + if (!pluginManifest) { + renderPluginSelector(renderer); return; } - toastNotification("Configuration pushed to GitHub successfully.", { - type: "success", - shouldAutoDismiss: true, - }); - - const container = document.querySelector("#manifest-gui") as HTMLElement | null; - const readmeContainer = document.querySelector(".readme-container") as HTMLElement | null; - if (container && readmeContainer) { - container.style.transition = "opacity 0.5s ease"; - container.style.opacity = "0"; - readmeContainer.style.transition = "opacity 0.5s ease"; - readmeContainer.style.opacity = "0"; - setTimeout(() => { - handleBackButtonClick(renderer); - container.style.opacity = "1"; - }, 500); - } else { - handleBackButtonClick(renderer); - } -} - -export function handleResetToDefault(renderer: ManifestRenderer, pluginManifest: Manifest | null) { - if (!pluginManifest) { - throw new Error("No plugin manifest found"); - } - renderConfigEditor(renderer, pluginManifest); - const readmeContainer = document.querySelector(".readme-container"); - if (readmeContainer) { - readmeContainer.remove(); - } + const configurationDefault = pluginManifest.configuration.default as Record; + writeNewConfig(renderer, pluginManifest, configurationDefault); } diff --git a/static/types/plugins.ts b/static/types/plugins.ts index 7f3404a..5b0bc85 100644 --- a/static/types/plugins.ts +++ b/static/types/plugins.ts @@ -15,6 +15,7 @@ export interface ManifestPreDecode extends Manifest { actionUrl?: string; workerUrl?: string; error?: string; + repoName?: string; } export type ManifestCache = Record; @@ -31,7 +32,7 @@ export type Manifest = { }; configuration: { type: string; - default: object; + default: Record; items?: { type: string; }; diff --git a/static/utils/strings.ts b/static/utils/strings.ts index bb65ebe..337d168 100644 --- a/static/utils/strings.ts +++ b/static/utils/strings.ts @@ -5,4 +5,17 @@ export const STRINGS = { SELECT_HIDE: "select-hide", SELECT_ARROW_ACTIVE: "select-arrow-active", PICKER_SELECT: "picker-select", + CONFIG_TABLE: "config-table", + CONFIG_TABLE_BODY: "config-table-body", + CONFIG_TABLE_HEADER: "config-table-header", + CONFIG_TABLE_HEADER_CELL: "config-table-header-cell", + CONFIG_ROW: "config-row", + CONFIG_LABEL_CELL: "config-label-cell", + CONFIG_INPUT_CELL: "config-input-cell", + CONFIG_INPUT: "config-input", + CONFIG_BUTTON_ROW: "config-button-row", + CONFIG_BUTTON_CELL: "config-button-cell", + CONFIG_SAVE_BUTTON: "config-save-button", + CONFIG_RESET_BUTTON: "config-reset-button", + CONFIG_REMOVE_BUTTON: "config-remove-button", };