diff --git a/static/manifest-gui.css b/static/manifest-gui.css index 44c1d8b..01ae511 100644 --- a/static/manifest-gui.css +++ b/static/manifest-gui.css @@ -200,7 +200,7 @@ button#add:active::before { } .config-input { - width: 100%; + width: calc(100% - 32px); padding: 8px 16px; background-color: #101010; color: #fff; diff --git a/static/scripts/config-parser.ts b/static/scripts/config-parser.ts index ad1643d..96b455a 100644 --- a/static/scripts/config-parser.ts +++ b/static/scripts/config-parser.ts @@ -39,8 +39,8 @@ export class ConfigParser { return YAML.parse(`${this.newConfigYml}`); } - async updateConfig(org: string, env: "development" | "production", octokit: Octokit) { - const repoPlugins = this.parseConfig(this.repoConfig).plugins; + async updateConfig(org: string, env: "development" | "production", octokit: Octokit, option: "add" | "remove") { + let repoPlugins = this.parseConfig(this.repoConfig).plugins; const newPlugins = this.parseConfig().plugins; if (!newPlugins) { @@ -52,13 +52,16 @@ export class ConfigParser { throw new Error("No plugins found in the config"); } - for (const plugin of newPlugins) { - const repoPlugin = repoPlugins.find((p) => p.uses[0].plugin === plugin.uses[0].plugin); - if (repoPlugin) { - repoPlugin.uses = plugin.uses; - } else { - repoPlugins.push(plugin); - } + if (option === "add") { + newPluginNames.forEach((pluginName) => { + const existingPlugin = repoPlugins.find((p) => p.uses[0].plugin === pluginName); + if (!existingPlugin) { + repoPlugins.push(newPlugins.find((p) => p.uses[0].plugin === pluginName) as Plugin); + } + }); + } else if (option === "remove") { + // remove only this plugin, keep all others + repoPlugins = repoPlugins.filter((p) => !newPluginNames.includes(p.uses[0].plugin)); } this.newConfigYml = YAML.stringify({ plugins: repoPlugins }); @@ -96,6 +99,19 @@ export class ConfigParser { this.saveConfig(); } + removePlugin(plugin: Plugin) { + const config = this.loadConfig(); + const parsedConfig = YAML.parse(config); + if (!parsedConfig.plugins) { + console.log("No plugins to remove"); + return; + } + parsedConfig.plugins = parsedConfig.plugins.filter((p: Plugin) => p.uses[0].plugin !== plugin.uses[0].plugin); + console.log(parsedConfig); + this.newConfigYml = YAML.stringify(parsedConfig); + this.saveConfig(); + } + /** * Loads the current config from local storage or * creates a new one if it doesn't exist. @@ -105,10 +121,6 @@ export class ConfigParser { * the ubiquity-os.config.yml file. */ loadConfig() { - if (this.repoConfig) { - return this.repoConfig; - } - if (!this.newConfigYml) { this.newConfigYml = localStorage.getItem("config"); } diff --git a/static/scripts/render-manifest.ts b/static/scripts/render-manifest.ts index ef2dd76..243c80b 100644 --- a/static/scripts/render-manifest.ts +++ b/static/scripts/render-manifest.ts @@ -14,6 +14,9 @@ export class ManifestRenderer { private _configParser = new ConfigParser(); private _configDefaults: { [key: string]: { type: string; value: string; items: { type: string } | null } } = {}; private _auth: AuthService; + private _backButton: HTMLButtonElement; + private _currentStep: "orgPicker" | "configSelector" | "pluginSelector" | "configEditor" = "orgPicker"; + private _orgs: string[] = []; constructor(auth: AuthService) { this._auth = auth; @@ -26,8 +29,39 @@ export class ManifestRenderer { this._manifestGui = manifestGui as HTMLElement; this._manifestGuiBody = manifestGuiBody as HTMLElement; - this._controlButtons(true); + + this._backButton = createElement("button", { + id: "back-button", + class: "button", + textContent: "Back", + }) as HTMLButtonElement; + + const title = manifestGui.querySelector("#manifest-gui-title"); + title?.previousSibling?.appendChild(this._backButton); + this._backButton.style.display = "none"; + this._backButton.addEventListener("click", this._handleBackButtonClick.bind(this)); + } + + private _handleBackButtonClick(): void { + switch (this._currentStep) { + case "configSelector": { + this.renderOrgPicker(this._orgs); + break; + } + case "pluginSelector": { + const selectedConfig = localStorage.getItem("selectedConfig") as "development" | "production"; + this._renderConfigSelector(selectedConfig); + break; + } + case "configEditor": { + const selectedConfig = localStorage.getItem("selectedConfig") as "development" | "production"; + this._renderPluginSelector(selectedConfig); + break; + } + default: + break; + } } // Event Handlers @@ -94,6 +128,10 @@ export class ManifestRenderer { } public renderOrgPicker(orgs: string[]): void { + this._orgs = orgs; + this._currentStep = "orgPicker"; + this._controlButtons(true); + this._backButton.style.display = "none"; this._manifestGui?.classList.add("rendering"); this._manifestGuiBody.innerHTML = ""; @@ -144,7 +182,10 @@ export class ManifestRenderer { } private _renderConfigSelector(selectedOrg: string): void { + this._currentStep = "configSelector"; + this._backButton.style.display = "block"; this._manifestGuiBody.innerHTML = ""; + this._controlButtons(true); const pickerRow = document.createElement("tr"); const pickerCell = document.createElement("td"); @@ -180,7 +221,10 @@ export class ManifestRenderer { } private _renderPluginSelector(selectedConfig: "development" | "production"): void { + this._currentStep = "pluginSelector"; + this._backButton.style.display = "block"; this._manifestGuiBody.innerHTML = ""; + this._controlButtons(true); const manifestCache = JSON.parse(localStorage.getItem("manifestCache") || "{}") as ManifestCache; const pluginUrls = Object.keys(manifestCache); @@ -263,7 +307,10 @@ export class ManifestRenderer { } private _renderConfigEditor(manifestStr: string): void { + this._currentStep = "configEditor"; + this._backButton.style.display = "block"; this._manifestGuiBody.innerHTML = ""; + this._controlButtons(false); const pluginManifest = JSON.parse(manifestStr) as Manifest; const configProps = pluginManifest.configuration?.properties || {}; @@ -273,10 +320,14 @@ export class ManifestRenderer { if (!add) { throw new Error("Add button not found"); } - add.addEventListener("click", this._writeNewConfig.bind(this)); + add.addEventListener("click", this._writeNewConfig.bind(this, "add")); + const remove = document.getElementById("remove"); + if (!remove) { + throw new Error("Remove button not found"); + } + remove.addEventListener("click", this._writeNewConfig.bind(this, "remove")); this._updateGuiTitle(`Editing Configuration for ${pluginManifest.name}`); - this._controlButtons(false); this._manifestGui?.classList.add("plugin-editor"); this._manifestGui?.classList.add("rendered"); } @@ -354,7 +405,7 @@ export class ManifestRenderer { } } - private _writeNewConfig(): void { + private _writeNewConfig(option: "add" | "remove"): void { const selectedManifest = localStorage.getItem("selectedPluginManifest"); if (!selectedManifest) { throw new Error("No selected plugin manifest found"); @@ -394,6 +445,14 @@ export class ManifestRenderer { ], }; + if (option === "add") { + this._handleAddPlugin(plugin, pluginManifest); + } else { + this._handleRemovePlugin(plugin, pluginManifest); + } + } + + private _handleAddPlugin(plugin: Plugin, pluginManifest: Manifest): void { this._configParser.addPlugin(plugin); toastNotification(`Configuration for ${pluginManifest.name} saved successfully. Do you want to push to GitHub?`, { type: "success", @@ -416,7 +475,48 @@ export class ManifestRenderer { } try { - await this._configParser.updateConfig(org, config, octokit); + await this._configParser.updateConfig(org, config, octokit, "add"); + } catch (error) { + console.error("Error pushing config to GitHub:", error); + toastNotification("An error occurred while pushing the configuration to GitHub.", { + type: "error", + shouldAutoDismiss: true, + }); + return; + } + + toastNotification("Configuration pushed to GitHub successfully.", { + type: "success", + shouldAutoDismiss: true, + }); + }, + }); + } + + private _handleRemovePlugin(plugin: Plugin, pluginManifest: Manifest): void { + this._configParser.removePlugin(plugin); + toastNotification(`Configuration for ${pluginManifest.name} removed successfully. Do you want to push to GitHub?`, { + type: "success", + actionText: "Push to GitHub", + action: async () => { + const octokit = this._auth.octokit; + if (!octokit) { + throw new Error("Octokit not found"); + } + + const org = localStorage.getItem("selectedOrg"); + const config = localStorage.getItem("selectedConfig") as "development" | "production"; + + if (!org) { + throw new Error("No selected org found"); + } + + if (!config) { + throw new Error("No selected config found"); + } + + try { + await this._configParser.updateConfig(org, config, octokit, "remove"); } catch (error) { console.error("Error pushing config to GitHub:", error); toastNotification("An error occurred while pushing the configuration to GitHub.", {