diff --git a/app/assets/javascripts/admin/components/themes-list-item.js.es6 b/app/assets/javascripts/admin/components/themes-list-item.js.es6 new file mode 100644 index 00000000000..fe5243585bf --- /dev/null +++ b/app/assets/javascripts/admin/components/themes-list-item.js.es6 @@ -0,0 +1,32 @@ +import { default as computed } from "ember-addons/ember-computed-decorators"; + +const MAX_COMPONENTS = 4; + +export default Ember.Component.extend({ + classNames: ["themes-list-item"], + classNameBindings: ["theme.active:active"], + hasComponents: Em.computed.gt("children.length", 0), + hasMore: Em.computed.gt("moreCount", 0), + + @computed( + "theme.component", + "theme.childThemes.@each.name", + "theme.childThemes.length" + ) + children() { + const theme = this.get("theme"); + const children = theme.get("childThemes"); + if (theme.get("component") || !children) { + return []; + } + return children.slice(0, MAX_COMPONENTS).map(t => t.get("name")); + }, + + @computed("theme.childThemes.length", "theme.component", "children.length") + moreCount(childrenCount, component) { + if (component || !childrenCount) { + return 0; + } + return childrenCount - MAX_COMPONENTS; + } +}); diff --git a/app/assets/javascripts/admin/components/themes-list.js.es6 b/app/assets/javascripts/admin/components/themes-list.js.es6 index 5e40d157fba..cf41e4dd33c 100644 --- a/app/assets/javascripts/admin/components/themes-list.js.es6 +++ b/app/assets/javascripts/admin/components/themes-list.js.es6 @@ -1,4 +1,84 @@ +import { THEMES, COMPONENTS } from "admin/models/theme"; +import { default as computed } from "ember-addons/ember-computed-decorators"; + +const NUM_ENTRIES = 8; + export default Ember.Component.extend({ + THEMES: THEMES, + COMPONENTS: COMPONENTS, + classNames: ["themes-list"], - hasThemes: Ember.computed.gt("themes.length", 0) + + hasThemes: Em.computed.gt("themesList.length", 0), + hasUserThemes: Em.computed.gt("userThemes.length", 0), + hasInactiveThemes: Em.computed.gt("inactiveThemes.length", 0), + + themesTabActive: Em.computed.equal("currentTab", THEMES), + componentsTabActive: Em.computed.equal("currentTab", COMPONENTS), + + @computed("themes", "components", "currentTab") + themesList(themes, components) { + if (this.get("themesTabActive")) { + return themes; + } else { + return components; + } + }, + + @computed( + "themesList", + "currentTab", + "themesList.@each.user_selectable", + "themesList.@each.default" + ) + inactiveThemes(themes) { + if (this.get("componentsTabActive")) { + return []; + } + return themes.filter( + theme => !theme.get("user_selectable") && !theme.get("default") + ); + }, + + @computed( + "themesList", + "currentTab", + "themesList.@each.user_selectable", + "themesList.@each.default" + ) + userThemes(themes) { + if (this.get("componentsTabActive")) { + return []; + } + themes = themes.filter( + theme => theme.get("user_selectable") || theme.get("default") + ); + return _.sortBy(themes, t => { + return [ + !t.get("default"), + !t.get("user_selectable"), + t.get("name").toLowerCase() + ]; + }); + }, + + didRender() { + let height = -1; + this.$(".themes-list-item") + .slice(0, NUM_ENTRIES) + .each(function() { + height += $(this).outerHeight(); + }); + if (height >= 485 && height <= 800) { + this.$(".themes-list-container").css("max-height", `${height}px`); + } + }, + + actions: { + changeView(newTab) { + if (newTab !== this.get("currentTab")) { + this.set("currentTab", newTab); + } + } + } }); diff --git a/app/assets/javascripts/admin/controllers/admin-customize-themes-show.js.es6 b/app/assets/javascripts/admin/controllers/admin-customize-themes-show.js.es6 index 40ce1b37c7b..c73d4467fd4 100644 --- a/app/assets/javascripts/admin/controllers/admin-customize-themes-show.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-customize-themes-show.js.es6 @@ -6,6 +6,7 @@ import { url } from "discourse/lib/computed"; import { popupAjaxError } from "discourse/lib/ajax-error"; import showModal from "discourse/lib/show-modal"; import ThemeSettings from "admin/models/theme-settings"; +import { THEMES, COMPONENTS } from "admin/models/theme"; const THEME_UPLOAD_VAR = 2; const SETTINGS_TYPE_ID = 5; @@ -111,9 +112,20 @@ export default Ember.Controller.extend({ }, @computed("model.component") - switchKey(component) { + convertKey(component) { const type = component ? "component" : "theme"; - return `admin.customize.theme.switch_${type}`; + return `admin.customize.theme.convert_${type}`; + }, + + @computed("model.component") + convertIcon(component) { + return component ? "cube" : ""; + }, + + @computed("model.component") + convertTooltip(component) { + const type = component ? "component" : "theme"; + return `admin.customize.theme.convert_${type}_tooltip`; }, @computed("model.settings") @@ -128,6 +140,48 @@ export default Ember.Controller.extend({ downloadUrl: url("model.id", "/admin/themes/%@"), + commitSwitchType() { + const model = this.get("model"); + const newValue = !model.get("component"); + model.set("component", newValue); + + if (newValue) { + // component + this.set("parentController.currentTab", COMPONENTS); + } else { + this.set("parentController.currentTab", THEMES); + } + + model + .saveChanges("component") + .then(() => { + this.set("colorSchemeId", null); + + model.setProperties({ + default: false, + color_scheme_id: null, + user_selectable: false, + child_themes: [], + childThemes: [] + }); + + this.get("parentController.model.content").forEach(theme => { + const children = Array.from(theme.get("childThemes")); + const rawChildren = Array.from(theme.get("child_themes") || []); + const index = children ? children.indexOf(model) : -1; + if (index > -1) { + children.splice(index, 1); + rawChildren.splice(index, 1); + theme.setProperties({ + childThemes: children, + child_themes: rawChildren + }); + } + }); + }) + .catch(popupAjaxError); + }, + actions: { updateToLatest() { this.set("updatingRemote", true); @@ -264,30 +318,26 @@ export default Ember.Controller.extend({ }, switchType() { - return bootbox.confirm( - I18n.t(`${this.get("switchKey")}_alert`), - I18n.t("no_value"), - I18n.t("yes_value"), - result => { - if (result) { - const model = this.get("model"); - model.set("component", !model.get("component")); - model - .saveChanges("component") - .then(() => { - this.set("colorSchemeId", null); - model.setProperties({ - default: false, - color_scheme_id: null, - user_selectable: false, - child_themes: [], - childThemes: [] - }); - }) - .catch(popupAjaxError); + const relatives = this.get("model.component") + ? this.get("parentThemes") + : this.get("model.childThemes"); + if (relatives && relatives.length > 0) { + const names = relatives.map(relative => relative.get("name")); + bootbox.confirm( + I18n.t(`${this.get("convertKey")}_alert`, { + relatives: names.join(", ") + }), + I18n.t("no_value"), + I18n.t("yes_value"), + result => { + if (result) { + this.commitSwitchType(); + } } - } - ); + ); + } else { + this.commitSwitchType(); + } } } }); diff --git a/app/assets/javascripts/admin/controllers/admin-customize-themes.js.es6 b/app/assets/javascripts/admin/controllers/admin-customize-themes.js.es6 index 8468634a0ad..641ec628260 100644 --- a/app/assets/javascripts/admin/controllers/admin-customize-themes.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-customize-themes.js.es6 @@ -1,18 +1,15 @@ import { default as computed } from "ember-addons/ember-computed-decorators"; +import { THEMES } from "admin/models/theme"; export default Ember.Controller.extend({ - @computed("model", "model.@each", "model.@each.component") + currentTab: THEMES, + + @computed("model", "model.@each.component") fullThemes(themes) { - return _.sortBy(themes.filter(t => !t.get("component")), t => { - return [ - !t.get("default"), - !t.get("user_selectable"), - t.get("name").toLowerCase() - ]; - }); + return themes.filter(t => !t.get("component")); }, - @computed("model", "model.@each", "model.@each.component") + @computed("model", "model.@each.component") childThemes(themes) { return themes.filter(t => t.get("component")); } diff --git a/app/assets/javascripts/admin/controllers/modals/admin-create-theme.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-create-theme.js.es6 index 343be61eb1e..4b25e006c25 100644 --- a/app/assets/javascripts/admin/controllers/modals/admin-create-theme.js.es6 +++ b/app/assets/javascripts/admin/controllers/modals/admin-create-theme.js.es6 @@ -1,27 +1,56 @@ import ModalFunctionality from "discourse/mixins/modal-functionality"; import { default as computed } from "ember-addons/ember-computed-decorators"; import { popupAjaxError } from "discourse/lib/ajax-error"; +import { THEMES, COMPONENTS } from "admin/models/theme"; -const COMPONENT = "component"; +const MIN_NAME_LENGTH = 4; export default Ember.Controller.extend(ModalFunctionality, { - types: [ - { name: I18n.t("admin.customize.theme.theme"), value: "theme" }, - { name: I18n.t("admin.customize.theme.component"), value: COMPONENT } - ], - selectedType: "theme", - name: I18n.t("admin.customize.new_style"), + saving: false, + triggerError: false, themesController: Ember.inject.controller("adminCustomizeThemes"), - loading: false, + types: [ + { name: I18n.t("admin.customize.theme.theme"), value: THEMES }, + { name: I18n.t("admin.customize.theme.component"), value: COMPONENTS } + ], + + @computed("triggerError", "nameTooShort") + showError(trigger, tooShort) { + return trigger && tooShort; + }, + + @computed("name") + nameTooShort(name) { + return !name || name.length < MIN_NAME_LENGTH; + }, + + @computed("component") + placeholder(component) { + if (component) { + return I18n.t("admin.customize.theme.component_name"); + } else { + return I18n.t("admin.customize.theme.theme_name"); + } + }, + + @computed("themesController.currentTab") + selectedType(tab) { + return tab; + }, @computed("selectedType") component(type) { - return type === COMPONENT; + return type === COMPONENTS; }, actions: { createTheme() { - this.set("loading", true); + if (this.get("nameTooShort")) { + this.set("triggerError", true); + return; + } + + this.set("saving", true); const theme = this.store.createRecord("theme"); theme .save({ name: this.get("name"), component: this.get("component") }) @@ -30,7 +59,7 @@ export default Ember.Controller.extend(ModalFunctionality, { this.send("closeModal"); }) .catch(popupAjaxError) - .finally(() => this.set("loading", false)); + .finally(() => this.set("saving", false)); } } }); diff --git a/app/assets/javascripts/admin/models/theme.js.es6 b/app/assets/javascripts/admin/models/theme.js.es6 index c3d7fd800da..fa37798fc01 100644 --- a/app/assets/javascripts/admin/models/theme.js.es6 +++ b/app/assets/javascripts/admin/models/theme.js.es6 @@ -4,6 +4,9 @@ import { popupAjaxError } from "discourse/lib/ajax-error"; const THEME_UPLOAD_VAR = 2; +export const THEMES = "themes"; +export const COMPONENTS = "components"; + const Theme = RestModel.extend({ FIELDS_IDS: [0, 1], @@ -33,6 +36,18 @@ const Theme = RestModel.extend({ ); }, + @computed("theme_fields", "theme_fields.@each.error") + isBroken(fields) { + return ( + fields && fields.some(field => field.error && field.error.length > 0) + ); + }, + + @computed("remote_theme", "remote_theme.commits_behind") + isPendingUpdates(remote, commitsBehind) { + return remote && commitsBehind && commitsBehind > 0; + }, + getKey(field) { return `${field.target} ${field.name}`; }, diff --git a/app/assets/javascripts/admin/routes/admin-customize-themes-show.js.es6 b/app/assets/javascripts/admin/routes/admin-customize-themes-show.js.es6 index e9ca55bb382..f3736ffa74a 100644 --- a/app/assets/javascripts/admin/routes/admin-customize-themes-show.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-customize-themes-show.js.es6 @@ -1,4 +1,5 @@ import { scrollTop } from "discourse/mixins/scroll-top"; +import { THEMES, COMPONENTS } from "admin/models/theme"; export default Ember.Route.extend({ serialize(model) { @@ -14,19 +15,21 @@ export default Ember.Route.extend({ setupController(controller, model) { this._super(...arguments); - controller.set("model", model); - const parentController = this.controllerFor("adminCustomizeThemes"); - parentController.set("editingTheme", false); - controller.set("allThemes", parentController.get("model")); + parentController.setProperties({ + editingTheme: false, + currentTab: model.get("component") ? COMPONENTS : THEMES + }); + + controller.setProperties({ + model: model, + parentController: parentController, + allThemes: parentController.get("model"), + colorSchemeId: model.get("color_scheme_id"), + colorSchemes: parentController.get("model.extras.color_schemes") + }); this.handleHighlight(model); - - controller.set( - "colorSchemes", - parentController.get("model.extras.color_schemes") - ); - controller.set("colorSchemeId", model.get("color_scheme_id")); }, deactivate() { diff --git a/app/assets/javascripts/admin/templates/components/themes-list-item.hbs b/app/assets/javascripts/admin/templates/components/themes-list-item.hbs new file mode 100644 index 00000000000..3cde5084b0d --- /dev/null +++ b/app/assets/javascripts/admin/templates/components/themes-list-item.hbs @@ -0,0 +1,34 @@ +{{#link-to 'adminCustomizeThemes.show' theme replace=true}} + {{plugin-outlet name="admin-customize-themes-list-item" connectorTagName='span' args=(hash theme=theme)}} + +
{{i18n 'admin.customize.about'}}
diff --git a/app/assets/javascripts/admin/templates/customize-themes-show.hbs b/app/assets/javascripts/admin/templates/customize-themes-show.hbs index 21090a03e92..1d490ade5e1 100644 --- a/app/assets/javascripts/admin/templates/customize-themes-show.hbs +++ b/app/assets/javascripts/admin/templates/customize-themes-show.hbs @@ -1,5 +1,5 @@- {{i18n "admin.customize.theme.about_theme"}} -
- {{#if model.remote_theme.license_url}} -- {{i18n "admin.customize.theme.license"}} {{d-icon "copyright"}} -
+ {{i18n "admin.customize.theme.about_theme"}} + {{#if model.remote_theme.license_url}} + {{i18n "admin.customize.theme.license"}} {{d-icon "copyright"}} + {{/if}} {{/if}} - {{/if}} - {{#if parentThemes}} -+
{{i18n "admin.customize.theme.color_scheme_select"}}
-{{combo-box content=colorSchemes - filterable=true - forceEscape=true - value=colorSchemeId - icon="paint-brush"}} - {{#if colorSchemeChanged}} - {{d-button action="changeScheme" class="btn-primary btn-small submit-edit" icon="check"}} - {{d-button action="cancelChangeScheme" class="btn-small cancel-edit" icon="times"}} - {{/if}} -
- {{#link-to 'adminCustomize.colors' class="btn edit"}}{{i18n 'admin.customize.colors.edit'}}{{/link-to}} +{{i18n "admin.customize.theme.custom_sections"}}
-- {{i18n "admin.customize.theme.edit_css_html_help"}} -
- {{/if}} +{{#if model.remote_theme}} {{#if model.remote_theme.commits_behind}} {{#d-button action="updateToLatest" icon="download" class='btn-primary'}}{{i18n "admin.customize.theme.update_to_latest"}}{{/d-button}} @@ -75,7 +74,6 @@ {{/if}} {{#d-button action="editTheme" class="btn edit"}}{{i18n 'admin.customize.theme.edit_css_html'}}{{/d-button}} - {{#if model.remote_theme}}
+ {{/if}} -{{i18n "admin.customize.theme.no_uploads"}}
- {{/if}} -+
- -
- {{else}} -- {{combo-box forceEscape=true filterable=true content=selectableChildThemes value=selectedChildThemeId}} - {{#d-button action="addChildTheme" icon="plus"}}{{i18n "admin.customize.theme.add"}}{{/d-button}} -
- {{/if}} +