From b124736a1c3ed0f3841b71d4ef73ddc8b6fd87d6 Mon Sep 17 00:00:00 2001 From: Martin Brennan Date: Tue, 5 Nov 2024 09:04:33 +1000 Subject: [PATCH] DEV: Convert install theme modal to GJS (#29506) We will likely get rid of this at some point, but in the meantime let's make it easier to modify and bring it up to date. --- .../addon/components/install-theme-item.gjs | 23 + .../addon/components/install-theme-item.hbs | 13 - .../addon/components/install-theme-item.js | 5 - .../addon/components/modal/install-theme.gjs | 468 ++++++++++++++++++ .../addon/components/modal/install-theme.hbs | 206 -------- .../addon/components/modal/install-theme.js | 234 --------- .../addon/templates/customize-themes-show.hbs | 4 +- 7 files changed, 493 insertions(+), 460 deletions(-) create mode 100644 app/assets/javascripts/admin/addon/components/install-theme-item.gjs delete mode 100644 app/assets/javascripts/admin/addon/components/install-theme-item.hbs delete mode 100644 app/assets/javascripts/admin/addon/components/install-theme-item.js create mode 100644 app/assets/javascripts/admin/addon/components/modal/install-theme.gjs delete mode 100644 app/assets/javascripts/admin/addon/components/modal/install-theme.hbs delete mode 100644 app/assets/javascripts/admin/addon/components/modal/install-theme.js diff --git a/app/assets/javascripts/admin/addon/components/install-theme-item.gjs b/app/assets/javascripts/admin/addon/components/install-theme-item.gjs new file mode 100644 index 00000000000..a415600aeed --- /dev/null +++ b/app/assets/javascripts/admin/addon/components/install-theme-item.gjs @@ -0,0 +1,23 @@ +import RadioButton from "discourse/components/radio-button"; +import dIcon from "discourse-common/helpers/d-icon"; +import i18n from "discourse-common/helpers/i18n"; + +const InstallThemeItem = ; + +export default InstallThemeItem; diff --git a/app/assets/javascripts/admin/addon/components/install-theme-item.hbs b/app/assets/javascripts/admin/addon/components/install-theme-item.hbs deleted file mode 100644 index 02bd8fa4389..00000000000 --- a/app/assets/javascripts/admin/addon/components/install-theme-item.hbs +++ /dev/null @@ -1,13 +0,0 @@ - - -{{d-icon "caret-right"}} \ No newline at end of file diff --git a/app/assets/javascripts/admin/addon/components/install-theme-item.js b/app/assets/javascripts/admin/addon/components/install-theme-item.js deleted file mode 100644 index d755c575060..00000000000 --- a/app/assets/javascripts/admin/addon/components/install-theme-item.js +++ /dev/null @@ -1,5 +0,0 @@ -import Component from "@ember/component"; -import { classNames } from "@ember-decorators/component"; - -@classNames("install-theme-item") -export default class InstallThemeItem extends Component {} diff --git a/app/assets/javascripts/admin/addon/components/modal/install-theme.gjs b/app/assets/javascripts/admin/addon/components/modal/install-theme.gjs new file mode 100644 index 00000000000..193ae3e42f9 --- /dev/null +++ b/app/assets/javascripts/admin/addon/components/modal/install-theme.gjs @@ -0,0 +1,468 @@ +import Component from "@glimmer/component"; +import { tracked } from "@glimmer/tracking"; +import { fn } from "@ember/helper"; +import { on } from "@ember/modifier"; +import { action } from "@ember/object"; +import didInsert from "@ember/render-modifiers/modifiers/did-insert"; +import { service } from "@ember/service"; +import { htmlSafe } from "@ember/template"; +import ConditionalLoadingSection from "discourse/components/conditional-loading-section"; +import CopyButton from "discourse/components/copy-button"; +import DButton from "discourse/components/d-button"; +import DModal from "discourse/components/d-modal"; +import withEventValue from "discourse/helpers/with-event-value"; +import { ajax } from "discourse/lib/ajax"; +import { popupAjaxError } from "discourse/lib/ajax-error"; +import dIcon from "discourse-common/helpers/d-icon"; +import i18n from "discourse-common/helpers/i18n"; +import { POPULAR_THEMES } from "discourse-common/lib/popular-themes"; +import I18n from "discourse-i18n"; +import InstallThemeItem from "admin/components/install-theme-item"; +import { COMPONENTS, THEMES } from "admin/models/theme"; +import ComboBox from "select-kit/components/combo-box"; + +const MIN_NAME_LENGTH = 4; +const CREATE_TYPES = [ + { name: I18n.t("admin.customize.theme.theme"), value: THEMES }, + { name: I18n.t("admin.customize.theme.component"), value: COMPONENTS }, +]; + +export default class InstallThemeModal extends Component { + @service store; + + @tracked selection = this.args.model.selection || "popular"; + @tracked uploadUrl = this.args.model.uploadUrl; + @tracked uploadName = this.args.model.uploadName; + @tracked selectedType = this.args.model.selectedType; + @tracked advancedVisible = false; + @tracked loading = false; + @tracked localFile; + @tracked publicKey; + @tracked branch; + @tracked duplicateRemoteThemeWarning; + @tracked themeCannotBeInstalled; + @tracked name; + + recordType = this.args.model.recordType || "theme"; + keyGenUrl = this.args.model.keyGenUrl || "/admin/themes/generate_key_pair"; + importUrl = this.args.model.importUrl || "/admin/themes/import"; + + get showPublicKey() { + return this.uploadUrl?.match?.(/^ssh:\/\/.+@.+$|.+@.+:.+$/); + } + + get submitLabel() { + if (this.themeCannotBeInstalled) { + return "admin.customize.theme.create_placeholder"; + } + + return `admin.customize.theme.${this.create ? "create" : "install"}`; + } + + get component() { + return this.selectedType === COMPONENTS; + } + + get local() { + return this.selection === "local"; + } + + get remote() { + return this.selection === "remote"; + } + + get create() { + return this.selection === "create"; + } + + get directRepoInstall() { + return this.selection === "directRepoInstall"; + } + + get popular() { + return this.selection === "popular"; + } + + get nameTooShort() { + return !this.name || this.name.length < MIN_NAME_LENGTH; + } + + get installDisabled() { + return ( + this.loading || + (this.remote && !this.uploadUrl) || + (this.local && !this.localFile) || + (this.create && this.nameTooShort) + ); + } + + get placeholder() { + if (this.component) { + return I18n.t("admin.customize.theme.component_name"); + } else { + return I18n.t("admin.customize.theme.theme_name"); + } + } + + get themes() { + return POPULAR_THEMES.map((popularTheme) => { + if ( + this.args.model.installedThemes.some((installedTheme) => + this.themeHasSameUrl(installedTheme, popularTheme.value) + ) + ) { + popularTheme.installed = true; + } + return popularTheme; + }); + } + + themeHasSameUrl(theme, url) { + const themeUrl = theme.remote_theme?.remote_url; + return ( + themeUrl && + url && + url.replace(/\.git$/, "") === themeUrl.replace(/\.git$/, "") + ); + } + + willDestroy() { + super.willDestroy(...arguments); + this.args.model.clearParams?.(); + } + + @action + async generatePublicKey() { + try { + const pair = await ajax(this.keyGenUrl, { + type: "POST", + }); + this.publicKey = pair.public_key; + } catch (err) { + popupAjaxError(err); + } + } + + @action + toggleAdvanced() { + this.advancedVisible = !this.advancedVisible; + } + + @action + uploadLocaleFile(event) { + this.localFile = event.target.files[0]; + } + + @action + updateSelectedType(type) { + this.args.model.updateSelectedType(type); + this.selectedType = type; + } + + @action + installThemeFromList(url) { + this.uploadUrl = url; + this.installTheme(); + } + + @action + async installTheme() { + if (this.create) { + this.loading = true; + const theme = this.store.createRecord(this.recordType); + try { + await theme.save({ name: this.name, component: this.component }); + this.args.model.addTheme(theme); + this.args.closeModal(); + } catch (err) { + popupAjaxError(err); + } finally { + this.loading = false; + } + return; + } + + let options = { + type: "POST", + }; + + if (this.local) { + options.processData = false; + options.contentType = false; + options.data = new FormData(); + options.data.append("theme", this.localFile); + } + + if (this.remote || this.popular || this.directRepoInstall) { + const duplicate = + this.args.model.content && + this.args.model.content.find((theme) => + this.themeHasSameUrl(theme, this.uploadUrl) + ); + if (duplicate && !this.duplicateRemoteThemeWarning) { + const warning = I18n.t("admin.customize.theme.duplicate_remote_theme", { + name: duplicate.name, + }); + this.duplicateRemoteThemeWarning = warning; + return; + } + options.data = { + remote: this.uploadUrl, + branch: this.branch, + public_key: this.publicKey, + }; + } + + // User knows that theme cannot be installed, but they want to continue + // to force install it. + if (this.themeCannotBeInstalled) { + options.data["force"] = true; + } + + // Used by theme-creator + if (this.args.model.userId) { + options.data["user_id"] = this.args.model.userId; + } + + try { + this.loading = true; + const result = await ajax(this.importUrl, options); + const theme = this.store.createRecord(this.recordType, result.theme); + this.args.model.addTheme(theme); + this.args.closeModal(); + } catch (err) { + if (!this.publicKey || this.themeCannotBeInstalled) { + return popupAjaxError(err); + } + this.themeCannotBeInstalled = I18n.t( + "admin.customize.theme.force_install" + ); + } finally { + this.loading = false; + } + } + +