diff --git a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 index 4a89343b..e0756650 100644 --- a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 +++ b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 @@ -1,11 +1,15 @@ -import { default as discourseComputed, observes, on } from 'discourse-common/utils/decorators'; +import { default as discourseComputed } from 'discourse-common/utils/decorators'; import { equal, empty, or } from "@ember/object/computed"; import { generateName, selectKitContent } from '../lib/wizard'; +import { computed } from "@ember/object"; import wizardSchema from '../lib/wizard-schema'; +import UndoChanges from '../mixins/undo-changes'; import Component from "@ember/component"; -export default Component.extend({ - classNames: 'wizard-custom-action', +export default Component.extend(UndoChanges, { + componentType: 'action', + classNameBindings: [':wizard-custom-action', 'visible'], + visible: computed('currentActionId', function() { return this.action.id === this.currentActionId }), actionTypes: Object.keys(wizardSchema.action.types).map(t => ({ id: t, name: generateName(t) })), createTopic: equal('action.type', 'create_topic'), updateProfile: equal('action.type', 'update_profile'), diff --git a/assets/javascripts/discourse/components/wizard-custom-field.js.es6 b/assets/javascripts/discourse/components/wizard-custom-field.js.es6 index 767258fd..660150b1 100644 --- a/assets/javascripts/discourse/components/wizard-custom-field.js.es6 +++ b/assets/javascripts/discourse/components/wizard-custom-field.js.es6 @@ -1,11 +1,14 @@ -import { default as discourseComputed, observes } from 'discourse-common/utils/decorators'; +import { default as discourseComputed } from 'discourse-common/utils/decorators'; import { equal, or } from "@ember/object/computed"; +import { computed } from "@ember/object"; import { selectKitContent } from '../lib/wizard'; -import { default as wizardSchema, setSchemaDefaults } from '../lib/wizard-schema'; +import UndoChanges from '../mixins/undo-changes'; import Component from "@ember/component"; -export default Component.extend({ - classNames: 'wizard-custom-field', +export default Component.extend(UndoChanges, { + componentType: 'field', + classNameBindings: [':wizard-custom-field', 'visible'], + visible: computed('currentFieldId', function() { return this.field.id === this.currentFieldId }), isDropdown: equal('field.type', 'dropdown'), isUpload: equal('field.type', 'upload'), isCategory: equal('field.type', 'category'), @@ -20,20 +23,6 @@ export default Component.extend({ showMinLength: or('isText', 'isTextarea', 'isUrl', 'isComposer'), categoryPropertyTypes: selectKitContent(['id', 'slug']), - // setTypeDefaults only set defaults if the field type of a specific field - // changes, and not when switching between fields. Switching between fields also - // changes the field.type property in this component - - @observes('field.id', 'field.type') - setTypeDefaults(ctx, changed) { - if (this.field.id === this.bufferedFieldId) { - setSchemaDefaults(this.field, 'field'); - } - if (changed === 'field.type') { - this.set('bufferedFieldId', this.field.id); - } - }, - setupTypeOutput(fieldType, options) { const selectionType = { category: 'category', @@ -84,7 +73,7 @@ export default Component.extend({ return this.setupTypeOutput(fieldType, options); }, - actions: { + actions: { imageUploadDone(upload) { this.set("field.image", upload.url); }, diff --git a/assets/javascripts/discourse/components/wizard-links.js.es6 b/assets/javascripts/discourse/components/wizard-links.js.es6 index 54a12a1a..308cd4bd 100644 --- a/assets/javascripts/discourse/components/wizard-links.js.es6 +++ b/assets/javascripts/discourse/components/wizard-links.js.es6 @@ -1,6 +1,6 @@ import { default as discourseComputed, on, observes } from 'discourse-common/utils/decorators'; import { generateName } from '../lib/wizard'; -import { default as wizardSchema, setSchemaDefaults } from '../lib/wizard-schema'; +import { default as wizardSchema, setWizardDefaults } from '../lib/wizard-schema'; import { notEmpty } from "@ember/object/computed"; import { scheduleOnce, bind } from "@ember/runloop"; import EmberObject from "@ember/object"; @@ -70,6 +70,10 @@ export default Component.extend({ add() { const items = this.get('items'); const itemType = this.itemType; + let params = setWizardDefaults({}, itemType); + + params.isNew = true; + let next = 1; if (items.length) { @@ -84,11 +88,8 @@ export default Component.extend({ if (itemType === 'field') { id = `${this.parentId}_${id}`; } - - let params = { - id, - isNew: true - }; + + params.id = id; let objectArrays = wizardSchema[itemType].objectArrays; if (objectArrays) { @@ -96,9 +97,7 @@ export default Component.extend({ params[objectArrays[objectType].property] = A(); }); }; - - setSchemaDefaults(params, itemType); - + const newItem = EmberObject.create(params); items.pushObject(newItem); diff --git a/assets/javascripts/discourse/components/wizard-mapper-connector.js.es6 b/assets/javascripts/discourse/components/wizard-mapper-connector.js.es6 index 946acaed..91f80164 100644 --- a/assets/javascripts/discourse/components/wizard-mapper-connector.js.es6 +++ b/assets/javascripts/discourse/components/wizard-mapper-connector.js.es6 @@ -3,6 +3,7 @@ import { gt } from '@ember/object/computed'; import { computed } from "@ember/object"; import { defaultConnector } from '../lib/wizard-mapper'; import { later } from "@ember/runloop"; +import { observes } from "discourse-common/utils/decorators"; export default Component.extend({ classNameBindings: [':mapper-connector', ':mapper-block', 'hasMultiple::single'], @@ -22,5 +23,10 @@ export default Component.extend({ ); }); } + }, + + @observes('connector') + updated() { + this.onUpdate('connector'); } }); \ No newline at end of file diff --git a/assets/javascripts/discourse/components/wizard-mapper-selector.js.es6 b/assets/javascripts/discourse/components/wizard-mapper-selector.js.es6 index 34f6a819..12e2a86e 100644 --- a/assets/javascripts/discourse/components/wizard-mapper-selector.js.es6 +++ b/assets/javascripts/discourse/components/wizard-mapper-selector.js.es6 @@ -167,6 +167,11 @@ export default Component.extend({ return this.activeType === type && this[`${type}Enabled`]; }, + @observes('activeType', 'value') + updated() { + this.onUpdate('selector'); + }, + actions: { toggleType(type) { this.set('activeType', type); diff --git a/assets/javascripts/discourse/components/wizard-mapper.js.es6 b/assets/javascripts/discourse/components/wizard-mapper.js.es6 index 525cf4ba..d50d0172 100644 --- a/assets/javascripts/discourse/components/wizard-mapper.js.es6 +++ b/assets/javascripts/discourse/components/wizard-mapper.js.es6 @@ -59,6 +59,8 @@ export default Component.extend({ this.get('inputs').pushObject( newInput(this.inputOptions, this.inputs.length) ); + + this.onUpdate(this.property, 'input'); }, remove(input) { @@ -68,6 +70,12 @@ export default Component.extend({ if (inputs.length) { inputs[0].set('connector', null); } + + this.onUpdate(this.property, 'input'); + }, + + inputUpdated(type) { + this.onUpdate(this.property, type); } } }); diff --git a/assets/javascripts/discourse/lib/wizard-json.js.es6 b/assets/javascripts/discourse/lib/wizard-json.js.es6 index 33b46707..06cdb83b 100644 --- a/assets/javascripts/discourse/lib/wizard-json.js.es6 +++ b/assets/javascripts/discourse/lib/wizard-json.js.es6 @@ -79,7 +79,7 @@ function buildObject(json, type) { Object.keys(json).forEach(prop => { props[prop] = buildProperty(json, prop, type) }); - + return EmberObject.create(props); } @@ -162,6 +162,7 @@ function buildProperties(json) { }; json = actionPatch(json); // to be removed - see above + props.actions = buildObjectArray(json.actions, 'action'); } else { listProperties('wizard').forEach(prop => { diff --git a/assets/javascripts/discourse/lib/wizard-schema.js.es6 b/assets/javascripts/discourse/lib/wizard-schema.js.es6 index 24d824c0..07ac1ad5 100644 --- a/assets/javascripts/discourse/lib/wizard-schema.js.es6 +++ b/assets/javascripts/discourse/lib/wizard-schema.js.es6 @@ -1,4 +1,4 @@ -import { set } from "@ember/object"; +import { set, get } from "@ember/object"; const wizard = { basic: { @@ -194,25 +194,27 @@ if (Discourse.SiteSettings.wizard_apis_enabled) { } } -export function setSchemaDefaults(obj, objType) { - let objSchema = wizardSchema[objType]; - let basicDefaults = objSchema.basic; - - Object.keys(basicDefaults).forEach(property => { - if (basicDefaults[property]) { - set(obj, property, basicDefaults[property]); +export function setWizardDefaults(obj, itemType, opts={}) { + const objSchema = wizardSchema[itemType]; + const basicDefaults = objSchema.basic; + const typeDefaults = objSchema.types[obj.type]; + + Object.keys(basicDefaults).forEach(property => { + let defaultValue = get(basicDefaults, property); + if (defaultValue) { + set(obj, property, defaultValue); } }); - if (objSchema.types && obj.type) { - let typeDefaults = objSchema.types[obj.type]; - + if (typeDefaults) { Object.keys(typeDefaults).forEach(property => { if (typeDefaults.hasOwnProperty(property)) { - set(obj, property, typeDefaults[property]); - } + set(obj, property, get(typeDefaults, property)); + } }); } + + return obj; } export default wizardSchema; \ No newline at end of file diff --git a/assets/javascripts/discourse/lib/wizard.js.es6 b/assets/javascripts/discourse/lib/wizard.js.es6 index b492e966..32408ce6 100644 --- a/assets/javascripts/discourse/lib/wizard.js.es6 +++ b/assets/javascripts/discourse/lib/wizard.js.es6 @@ -49,11 +49,23 @@ const userProperties = [ 'trust_level' ]; -function listProperties(type, objectType = null) { +function listProperties(type, opts={}) { let properties = Object.keys(wizardSchema[type].basic); - - if (wizardSchema[type].types && objectType) { - properties = properties.concat(Object.keys(wizardSchema[type].types[objectType])); + + const types = wizardSchema[type].types; + + if (types) { + let typeProperties = []; + + if (opts.allTypes) { + Object.keys(types).forEach(type => { + typeProperties = typeProperties.concat(Object.keys(types[type])); + }); + } else if (opts.objectType) { + typeProperties = Object.keys(types[opts.objectType]); + } + + properties = properties.concat(typeProperties); } return properties; diff --git a/assets/javascripts/discourse/mixins/undo-changes.js.es6 b/assets/javascripts/discourse/mixins/undo-changes.js.es6 new file mode 100644 index 00000000..b273de3b --- /dev/null +++ b/assets/javascripts/discourse/mixins/undo-changes.js.es6 @@ -0,0 +1,124 @@ +import { listProperties } from '../lib/wizard'; +import { default as wizardSchema } from '../lib/wizard-schema'; +import { set, get } from "@ember/object"; +import Mixin from "@ember/object/mixin"; +import { observes } from 'discourse-common/utils/decorators'; + +export default Mixin.create({ + didInsertElement() { + this._super(...arguments); + this.setupObservers(); + + const obj = this.get(this.componentType); + + this.setProperties({ + originalObject: JSON.parse(JSON.stringify(obj)), + undoIcon: obj.isNew ? 'times' : 'undo', + undoKey: `admin.wizard.${obj.isNew ? 'clear' : 'undo'}` + }) + }, + + willDestroyElement() { + this._super(...arguments); + this.removeObservers(); + }, + + removeObservers(objType=null) { + const componentType = this.componentType; + const obj = this.get(componentType); + + let opts = { + objectType: objType || obj.type + } + + listProperties(componentType, opts).forEach(property => { + obj.removeObserver(property, this, this.toggleUndo); + }); + }, + + setupObservers(objType=null) { + const componentType = this.componentType; + const obj = this.get(componentType); + + let opts = { + objectType: objType || obj.type + } + + listProperties(componentType, opts).forEach(property => { + obj.addObserver(property, this, this.toggleUndo); + }); + }, + + revertToOriginal(revertBasic=false) { + const original = JSON.parse(JSON.stringify(this.originalObject)); + const componentType = this.componentType; + const obj = this.get(componentType); + const objSchema = wizardSchema[componentType]; + const basicDefaults = objSchema.basic; + + if (revertBasic) { + Object.keys(basicDefaults).forEach(property => { + let value; + + if (original.hasOwnProperty(property)) { + value = get(original, property); + } else if (basicDefaults.hasOwnProperty(property)) { + value = get(basicDefaults, property); + } + + set(obj, property, value); + }); + } + + if (objSchema.types && obj.type) { + let typeDefaults = objSchema.types[obj.type]; + + Object.keys(typeDefaults).forEach(property => { + let value; + + if (original.type === obj.type && original.hasOwnProperty(property)) { + value = get(original, property); + } else if (typeDefaults.hasOwnProperty(property)) { + value = get(typeDefaults, property); + } + + set(obj, property, value); + }); + } + }, + + toggleUndo() { + const current = this.get(this.componentType); + const original = this.originalObject; + this.set('showUndo', !_.isEqual(current, original)); + }, + + actions: { + undoChanges() { + const componentType = this.componentType; + const original = this.get('originalObject'); + const obj = this.get(componentType); + + this.removeObservers(obj.type); + this.revertToOriginal(true); + this.set('showUndo', false); + this.setupObservers(this.get(componentType).type); + }, + + changeType(type) { + const componentType = this.componentType; + const original = this.get('originalObject'); + const obj = this.get(componentType); + + this.removeObservers(obj.type); + obj.set('type', type); + this.revertToOriginal(); + this.set('showUndo', type !== original.type); + this.setupObservers(type); + }, + + mappedFieldUpdated(property, type) { + this.get(this.componentType).notifyPropertyChange(property); + } + } +}) \ No newline at end of file diff --git a/assets/javascripts/discourse/models/custom-wizard.js.es6 b/assets/javascripts/discourse/models/custom-wizard.js.es6 index de3c2d80..7a842987 100644 --- a/assets/javascripts/discourse/models/custom-wizard.js.es6 +++ b/assets/javascripts/discourse/models/custom-wizard.js.es6 @@ -49,7 +49,7 @@ const CustomWizard = EmberObject.extend({ } } - for (let property of listProperties(type, objectType)) { + for (let property of listProperties(type, { objectType })) { let value = object.get(property); result = this.validateValue(property, value, object, type, result); diff --git a/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 index 69245453..14fb07e4 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 @@ -22,7 +22,7 @@ export default DiscourseRoute.extend({ const parentModel = this.modelFor('adminWizardsWizard'); const wizard = CustomWizard.create((!model || model.create) ? {} : model); - controller.setProperties({ + let props = { wizardList: parentModel.wizard_list, fieldTypes: selectKitContent(Object.keys(parentModel.field_types)), userFields: parentModel.userFields, @@ -32,6 +32,8 @@ export default DiscourseRoute.extend({ currentStep: wizard.steps[0], currentAction: wizard.actions[0], creating: model.create - }); + }; + + controller.setProperties(props); } }); diff --git a/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs b/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs index e0d511c1..81075e49 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs @@ -175,13 +175,14 @@ items=wizard.actions generateLabels=true}} - {{#if currentAction}} + {{#each wizard.actions as |action|}} {{wizard-custom-action - action=currentAction + action=action + currentActionId=currentAction.id wizard=wizard removeAction="removeAction" wizardFields=wizardFields}} - {{/if}} + {{/each}}