From d7afa3d39ea9c89d72868fc4c6407e552287765a Mon Sep 17 00:00:00 2001 From: Eugene Glova Date: Tue, 20 Jun 2017 12:10:21 +0300 Subject: [PATCH 01/12] Use raw json to set object schema for form --- .../content-types/content-type-page.jsx | 110 ++++++++++++++++++ ashes/src/modules/object-schema.js | 7 +- 2 files changed, 116 insertions(+), 1 deletion(-) diff --git a/ashes/src/components/content-types/content-type-page.jsx b/ashes/src/components/content-types/content-type-page.jsx index b9f9367b81..e98680fab1 100644 --- a/ashes/src/components/content-types/content-type-page.jsx +++ b/ashes/src/components/content-types/content-type-page.jsx @@ -13,6 +13,116 @@ import { transitionTo } from 'browserHistory'; import * as ContentTypeActions from 'modules/content-types/details'; class ContentTypePage extends ObjectPage { + componentDidMount() { + this.props.actions.clearFetchErrors(); + // this.props.actions.fetchSchema(this.props.namespace, true); + this.props.actions.fetchSchema('json', + [ + { + "name": "content-type", + "kind": "contentType", + "schema": { + "type": "object", + "title": "Content Type", + "$schema": "http:\/\/json-schema.org\/draft-04\/schema#", + "properties": { + "discounts": { + "type": "array", + "items": { + "$ref": "#\/definitions\/discount" + } + }, + "attributes": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "minLength": 1 + }, + "activeTo": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "activeFrom": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "customerGroupIds": { + "type": [ + "array", + "null" + ], + "items": { + "type": "number" + }, + "uniqueItems": true + } + } + } + }, + "definitions": { + "discount": { + "type": "object", + "title": "Discount", + "$schema": "http:\/\/json-schema.org\/draft-04\/schema#", + "properties": { + "id": { + "type": "number" + }, + "attributes": { + "type": "object", + "properties": { + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "offer": { + "type": "object" + }, + "title": { + "type": "string" + }, + "qualifier": { + "type": "object" + }, + "description": { + "type": "string", + "widget": "richText" + } + } + } + } + } + } + } + } + ] + ); + + if (this.isNew) { + this.props.actions.newEntity(); + } else { + this.fetchEntity() + .then(({ payload }) => { + if (isArchived(payload)) this.transitionToList(); + }); + } + + this.props.actions.fetchAmazonStatus() + .catch(() => {}); // pass + } + save(): ?Promise<*> { let isNew = this.isNew; let willBePromo = super.save(); diff --git a/ashes/src/modules/object-schema.js b/ashes/src/modules/object-schema.js index b94bab28f3..7e6b948a5d 100644 --- a/ashes/src/modules/object-schema.js +++ b/ashes/src/modules/object-schema.js @@ -6,7 +6,12 @@ import Api from 'lib/api'; const _fetchSchema = createAsyncActions( 'fetchSchema', - (kind, id = void 0) => Api.get(`/object/schemas/byKind/${kind}`) + (kind, id = void 0) => { + if (kind === 'json') { + return Promise.resolve(id); + } + return Api.get(`/object/schemas/byKind/${kind}`); + } ); export const saveSchema = createAction('SCHEMA_SAVE', (kind, schema) => [kind, schema]); From 89ee47b82b5a369f794f658e173dbf8094271698 Mon Sep 17 00:00:00 2001 From: Eugene Glova Date: Tue, 20 Jun 2017 13:01:10 +0300 Subject: [PATCH 02/12] Fix name --- ashes/src/components/content-types/content-type-form.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ashes/src/components/content-types/content-type-form.jsx b/ashes/src/components/content-types/content-type-form.jsx index f8515745f5..09bbbc60da 100644 --- a/ashes/src/components/content-types/content-type-form.jsx +++ b/ashes/src/components/content-types/content-type-form.jsx @@ -21,7 +21,7 @@ import { setObjectAttr, omitObjectAttr } from 'paragons/object'; import { customerGroups } from 'paragons/object-types'; const layout = require('./layout.json'); -export default class PromotionForm extends ObjectDetails { +export default class ContentTypeForm extends ObjectDetails { layout = layout; From b668c17157f58085cf6ccdaf95d148578d91bb4c Mon Sep 17 00:00:00 2001 From: Eugene Glova Date: Tue, 20 Jun 2017 15:32:55 +0300 Subject: [PATCH 03/12] Add wrapper for section --- ashes/src/components/object-page/object-details.css | 9 +++++++++ ashes/src/components/object-page/object-details.jsx | 12 +++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/ashes/src/components/object-page/object-details.css b/ashes/src/components/object-page/object-details.css index 847a10e652..f05f1b1ff7 100644 --- a/ashes/src/components/object-page/object-details.css +++ b/ashes/src/components/object-page/object-details.css @@ -13,6 +13,15 @@ } } +.full-page { + width: 100%; + min-width: 400px; + + & > div { + margin-bottom: 20px; + } +} + .main { width: calc(67% - 1.85%); margin-right: 1.85%; diff --git a/ashes/src/components/object-page/object-details.jsx b/ashes/src/components/object-page/object-details.jsx index a459e5c2e3..0edf3dcd8c 100644 --- a/ashes/src/components/object-page/object-details.jsx +++ b/ashes/src/components/object-page/object-details.jsx @@ -186,6 +186,16 @@ export default class ObjectDetails extends Component { return addKeys(name, section.map(desc => this.renderNode(desc, section))); } + get main() { + if (this.layout.main != null) { + return ( +
+ {this.renderSection('main')} +
+ ); + } + } + get aside() { if (this.layout.aside != null) { return ( @@ -202,7 +212,7 @@ export default class ObjectDetails extends Component { return (
- {this.renderSection('main')} + {this.main}
{this.aside}
From 5067df909011691c7b893f45204a0cf5c771d401 Mon Sep 17 00:00:00 2001 From: Eugene Glova Date: Wed, 21 Jun 2017 13:26:23 +0300 Subject: [PATCH 04/12] Create columns --- .../content-types/content-type-form.jsx | 55 +++++++++++++++---- .../content-types/content-type-page.jsx | 26 ++------- .../src/components/content-types/layout.json | 47 ++-------------- .../components/object-page/object-details.css | 35 ++++++++++++ 4 files changed, 89 insertions(+), 74 deletions(-) diff --git a/ashes/src/components/content-types/content-type-form.jsx b/ashes/src/components/content-types/content-type-form.jsx index 09bbbc60da..a7ba1d976c 100644 --- a/ashes/src/components/content-types/content-type-form.jsx +++ b/ashes/src/components/content-types/content-type-form.jsx @@ -11,11 +11,13 @@ import styles from '../object-page/object-details.css'; import ObjectDetails from '../object-page/object-details'; import { FormField } from '../forms'; import RadioButton from '../forms/radio-button'; -import SelectCustomerGroups from '../customers-groups/select-groups'; import DiscountAttrs from './discount-attrs'; import offers from './offers'; import qualifiers from './qualifiers'; +import ContentBox from 'components/content-box/content-box'; +import { Button } from 'components/core/button'; + import { setDiscountAttr } from 'paragons/promotion'; import { setObjectAttr, omitObjectAttr } from 'paragons/object'; import { customerGroups } from 'paragons/object-types'; @@ -171,18 +173,47 @@ export default class ContentTypeForm extends ObjectDetails { this.props.onUpdateObject(newPromotion); } - renderCustomers(): Element<*> { - const promotion = this.props.object; + column(title: string, children): Element<*> { + const footer = ( +
+ +
+ ); + + return ( + + {children} + + ); + } + + renderColumns(): Element<*> { + const details = ( + + ); + return ( -
-
Customers
- +
+ {this.column('Tab', details)} + {this.column('Section')} + {this.column('Properties')} + {this.column('Property Settings')}
); } diff --git a/ashes/src/components/content-types/content-type-page.jsx b/ashes/src/components/content-types/content-type-page.jsx index e98680fab1..673f0a1e14 100644 --- a/ashes/src/components/content-types/content-type-page.jsx +++ b/ashes/src/components/content-types/content-type-page.jsx @@ -35,36 +35,22 @@ class ContentTypePage extends ObjectPage { "attributes": { "type": "object", "required": [ - "name" + "title" ], "properties": { - "name": { + "title": { "type": "string", "minLength": 1 }, - "activeTo": { + "description": { "type": [ "string", "null" ], - "format": "date-time" }, - "activeFrom": { - "type": [ - "string", - "null" - ], - "format": "date-time" - }, - "customerGroupIds": { - "type": [ - "array", - "null" - ], - "items": { - "type": "number" - }, - "uniqueItems": true + "slug": { + "type": "string", + "minLength": 1 } } } diff --git a/ashes/src/components/content-types/layout.json b/ashes/src/components/content-types/layout.json index cfe43deb50..4605ff4710 100644 --- a/ashes/src/components/content-types/layout.json +++ b/ashes/src/components/content-types/layout.json @@ -5,11 +5,13 @@ "title": "General", "content": [ { - "canAddProperty": true, + "canAddProperty": false, "includeRest": true, "type": "fields", "value": [ - "name" + "title", + "description", + "slug" ], "omit": [ "storefrontName", @@ -25,46 +27,7 @@ ] }, { - "type": "group", - "title": "Discounts", - "content": [ - { - "type": "discounts" - }, - { - "type": "customers" - } - ] - }, - { - "type": "group", - "title": "Usage Rules", - "content": [ - { - "type": "usage-rules" - } - ] - }, - { - "type": "group", - "showIfNew": true, - "title": "Apply Type", - "content": [ - { - "type": "apply-type" - } - ] - } - ], - "aside": [ - { - "type": "state" - }, - { - "type": "tags" - }, - { - "type": "watchers" + "type": "columns" } ] } diff --git a/ashes/src/components/object-page/object-details.css b/ashes/src/components/object-page/object-details.css index f05f1b1ff7..1cb8a0065d 100644 --- a/ashes/src/components/object-page/object-details.css +++ b/ashes/src/components/object-page/object-details.css @@ -56,3 +56,38 @@ .customer-groups { margin-top: 20px; } + +.columns { + display: -webkit-flex; + display: flex; + -webkit-flex-direction: row; + flex-direction: row; +} + +.column { + flex-grow: 1; + margin-top: 20px; + margin-right: 0; + border-left: 0; + + &:first-child { + border-left: 1px solid #d9d9d9; + } +} + +.column-body { + min-height: 600px; +} + +.column-body > * { + width: 100%; +} + +.column-footer { + padding: 10px; + border-top: 1px solid #d9d9d9; +} + +.column-footer > button { + width: 100%; +} From 5df8248517a786ac7c4572cb1fd27791c840b1c2 Mon Sep 17 00:00:00 2001 From: Eugene Glova Date: Wed, 21 Jun 2017 14:52:28 +0300 Subject: [PATCH 05/12] Add more styles --- .../content-types/content-type-form.jsx | 17 ++++++++++++++++- .../components/object-page/object-details.css | 7 +++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/ashes/src/components/content-types/content-type-form.jsx b/ashes/src/components/content-types/content-type-form.jsx index a7ba1d976c..9e17231488 100644 --- a/ashes/src/components/content-types/content-type-form.jsx +++ b/ashes/src/components/content-types/content-type-form.jsx @@ -5,6 +5,7 @@ import _ from 'lodash'; import React, { Element } from 'react'; import { autobind } from 'core-decorators'; import { assoc } from 'sprout-data'; +import classNames from 'classnames'; import styles from '../object-page/object-details.css'; @@ -185,16 +186,30 @@ export default class ContentTypeForm extends ObjectDetails {
); + const isEmpty = !children; + + const emptyBody = !isEmpty ? null : ( + + {`Add a ${title.toLowerCase()}`} + + ); + + const bodyClassName = classNames( + styles['column-body'], + {[styles['column-body-empty']]: isEmpty} + ); + return ( {children} + {emptyBody} ); } diff --git a/ashes/src/components/object-page/object-details.css b/ashes/src/components/object-page/object-details.css index 1cb8a0065d..64fef0a7ca 100644 --- a/ashes/src/components/object-page/object-details.css +++ b/ashes/src/components/object-page/object-details.css @@ -83,6 +83,13 @@ width: 100%; } +.column-body-empty { + display: flex; + flex-direction: column; + justify-content: center; + text-align: center; +} + .column-footer { padding: 10px; border-top: 1px solid #d9d9d9; From 17f590e3bb4f7e9278fcc957c89b7de9c3b8973a Mon Sep 17 00:00:00 2001 From: Eugene Glova Date: Wed, 21 Jun 2017 15:25:12 +0300 Subject: [PATCH 06/12] Make plus button to show popup --- .../content-types/content-type-form.jsx | 68 +++++++++++++++++-- 1 file changed, 64 insertions(+), 4 deletions(-) diff --git a/ashes/src/components/content-types/content-type-form.jsx b/ashes/src/components/content-types/content-type-form.jsx index 9e17231488..f0d6169890 100644 --- a/ashes/src/components/content-types/content-type-form.jsx +++ b/ashes/src/components/content-types/content-type-form.jsx @@ -16,7 +16,9 @@ import DiscountAttrs from './discount-attrs'; import offers from './offers'; import qualifiers from './qualifiers'; +import { ModalContainer } from 'components/modal/base'; import ContentBox from 'components/content-box/content-box'; +import SaveCancel from 'components/core/save-cancel'; import { Button } from 'components/core/button'; import { setDiscountAttr } from 'paragons/promotion'; @@ -28,6 +30,13 @@ export default class ContentTypeForm extends ObjectDetails { layout = layout; + state = { + tab: {}, + section: {}, + properties: {}, + 'property settings': {} + } + renderApplyType() { const promotion = this.props.object; return ( @@ -174,12 +183,64 @@ export default class ContentTypeForm extends ObjectDetails { this.props.onUpdateObject(newPromotion); } + @autobind + setIsVisible(title, value) { + return e => { + e.preventDefault(); + + const key = title.toLowerCase(); + + this.setState({ + [key]: { + ...this.state[key], + showModal: value + } + }); + }; + } + + modal(title: string): Element<*> { + const onCancel = this.setIsVisible(title, false); + + const modalActionBlock = ( + + + + ); + + const modalFooter = ( + + ); + + return ( + + +
+ test +
+
+
+ ); + } + column(title: string, children): Element<*> { + const onAdd = this.setIsVisible(title, true); + const footer = (
@@ -210,15 +271,14 @@ export default class ContentTypeForm extends ObjectDetails { > {children} {emptyBody} + {this.modal(title)} ); } renderColumns(): Element<*> { const details = ( - ); From 10223e5ff5343f7ab79a3f857920cbf6b6eeffdf Mon Sep 17 00:00:00 2001 From: Eugene Glova Date: Mon, 26 Jun 2017 17:21:36 +0300 Subject: [PATCH 07/12] Allow to add sections with modal --- .../content-types/content-type-form.jsx | 113 ++++++++++-------- ashes/src/components/content-types/modal.js | 69 +++++++++++ ashes/src/paragons/content-type.js | 42 +++---- 3 files changed, 145 insertions(+), 79 deletions(-) create mode 100644 ashes/src/components/content-types/modal.js diff --git a/ashes/src/components/content-types/content-type-form.jsx b/ashes/src/components/content-types/content-type-form.jsx index 727de8ecbf..621a09504a 100644 --- a/ashes/src/components/content-types/content-type-form.jsx +++ b/ashes/src/components/content-types/content-type-form.jsx @@ -10,16 +10,14 @@ import classNames from 'classnames'; import styles from '../object-page/object-details.css'; import ObjectDetails from '../object-page/object-details'; +import Modal from './modal'; import { FormField } from '../forms'; import RadioButton from 'components/core/radio-button'; -import SelectCustomerGroups from '../customers-groups/select-groups'; import DiscountAttrs from './discount-attrs'; import offers from './offers'; import qualifiers from './qualifiers'; -import { ModalContainer } from 'components/modal/base'; import ContentBox from 'components/content-box/content-box'; -import SaveCancel from 'components/core/save-cancel'; import { Button } from 'components/core/button'; import { setDiscountAttr } from 'paragons/promotion'; @@ -32,10 +30,10 @@ export default class ContentTypeForm extends ObjectDetails { layout = layout; state = { - tab: {}, - section: {}, + tabs: {}, + sections: {}, properties: {}, - 'property settings': {} + 'property-settings': {} } renderApplyType() { @@ -185,12 +183,8 @@ export default class ContentTypeForm extends ObjectDetails { } @autobind - setIsVisible(title, value) { - return e => { - e.preventDefault(); - - const key = title.toLowerCase(); - + setIsVisible(key, value) { + return () => { this.setState({ [key]: { ...this.state[key], @@ -200,42 +194,63 @@ export default class ContentTypeForm extends ObjectDetails { }; } - modal(title: string): Element<*> { - const onCancel = this.setIsVisible(title, false); + @autobind + onSave(key) { + return object => { + this.props.onUpdateObject({ + ...this.props.object, + [key]: [ + ...this.props.object[key], + { + attributes: object + } + ] + }); + }; + } - const modalActionBlock = ( - - - - ); + @autobind + onCancel(key) { + return this.setIsVisible(key, false); + } - const modalFooter = ( - - ); + modal(key: string, title: string): Element<*> { + const schema = { + "type": "object", + "required": [ + "title" + ], + "properties": { + "title": { + "type": "string", + "minLength": 1 + }, + "slug": { + "type": "string", + "minLength": 1 + }, + "custom-properties": { + "title": "Custom Properties can be added to this section", + "type": "boolean" + } + } + }; return ( - - -
- test -
-
-
+ ); } - column(title: string, children): Element<*> { - const onAdd = this.setIsVisible(title, true); + column(key: string, title: string, children): Element<*> { + const onAdd = this.setIsVisible(key, true); const footer = (
@@ -272,24 +287,18 @@ export default class ContentTypeForm extends ObjectDetails { > {children} {emptyBody} - {this.modal(title)} + {this.modal(key, title)} ); } renderColumns(): Element<*> { - const details = ( - - ); - return (
- {this.column('Tab', details)} - {this.column('Section')} - {this.column('Properties')} - {this.column('Property Settings')} + {this.column('tabs', 'Tab', _.map(this.props.object.tabs, (tab) => ))} + {this.column('sections', 'Section', _.map(this.props.object.sections, (section) => ))} + {this.column('properties', 'Properties')} + {this.column('property-settings', 'Property Settings')}
); } diff --git a/ashes/src/components/content-types/modal.js b/ashes/src/components/content-types/modal.js new file mode 100644 index 0000000000..a860756f11 --- /dev/null +++ b/ashes/src/components/content-types/modal.js @@ -0,0 +1,69 @@ +/* @flow */ + +// libs +import React, { Component, Element } from 'react'; +import { autobind } from 'core-decorators'; + +// components +import Modal from 'components/core/modal'; +import ObjectFormInner from 'components/object-form/object-form-inner'; +import SaveCancel from 'components/core/save-cancel'; + +type Props = { + title: string, + isVisible: boolean, + schema: object, + object: object, + fieldsToRender: Array, + onCancel: Function, + onUpdateObject: Function, +}; + +type State = { + object: object, +}; + +export default class MyModal extends Component { + props: Props; + + state: State = { + object: {}, + }; + + @autobind + handleChange(object: object) { + this.setState({ object }); + } + + @autobind + handleSave() { + this.props.onSave(this.state.object); + this.props.onCancel(); + } + + get footer() { + const saveDisabled = false; + + return ; + } + + render() { + const props = this.props; + + const attributes = { + ...props.object, + ...this.state.object, + }; + + return ( + + + + ); + } +} diff --git a/ashes/src/paragons/content-type.js b/ashes/src/paragons/content-type.js index 8e4d585b8d..e9574c8061 100644 --- a/ashes/src/paragons/content-type.js +++ b/ashes/src/paragons/content-type.js @@ -1,50 +1,38 @@ import { assoc } from 'sprout-data'; -function addEmptyDiscount(contentType) { - const discount = { +function addEmptyTab(contentType) { + const tab = { id: null, createdAt: null, attributes: { - qualifier: { - t: 'qualifier', - v: { - orderAny: {} - } + title: { + t: 'string', + v: 'Details', }, - offer: { - t: 'offer', - v: { - orderPercentOff: {} - } - } - }, + } }; - contentType.discounts.push(discount); + contentType.tabs.push(tab); return contentType; } export function createEmptyContentType() { const contentType = { id: null, - applyType: 'auto', - isExclusive: true, createdAt: null, attributes: { - storefrontName: { - t: 'richText', - v: 'Storefront name' - }, - customerGroupIds: { - t: 'tock673sjgmqbi5zlfx43o4px6jnxi7absotzjvxwir7jo2v', - v: null, - }, + title: null, + description: null, + slug: null }, - discounts: [], + tabs: [], + sections: [], + properties: [], + 'property-settings': [] }; - return addEmptyDiscount(contentType); + return addEmptyTab(contentType); } export function setDiscountAttr(contentType, label, value) { From 7c3db41bf6bfed42a9c4b5b14325fa39b803e185 Mon Sep 17 00:00:00 2001 From: Eugene Glova Date: Mon, 3 Jul 2017 15:36:24 +0300 Subject: [PATCH 08/12] Make property settings form rendering and editing it's values --- .../content-types/content-type-form.jsx | 276 ++++++++++++++---- 1 file changed, 219 insertions(+), 57 deletions(-) diff --git a/ashes/src/components/content-types/content-type-form.jsx b/ashes/src/components/content-types/content-type-form.jsx index 621a09504a..9c11d5a528 100644 --- a/ashes/src/components/content-types/content-type-form.jsx +++ b/ashes/src/components/content-types/content-type-form.jsx @@ -11,6 +11,7 @@ import styles from '../object-page/object-details.css'; import ObjectDetails from '../object-page/object-details'; import Modal from './modal'; +import Form from './form'; import { FormField } from '../forms'; import RadioButton from 'components/core/radio-button'; import DiscountAttrs from './discount-attrs'; @@ -33,7 +34,8 @@ export default class ContentTypeForm extends ObjectDetails { tabs: {}, sections: {}, properties: {}, - 'property-settings': {} + 'property-settings': {}, + currentPropertyIndex: 0 } renderApplyType() { @@ -195,17 +197,29 @@ export default class ContentTypeForm extends ObjectDetails { } @autobind - onSave(key) { + onSave(key, index) { return object => { - this.props.onUpdateObject({ - ...this.props.object, - [key]: [ - ...this.props.object[key], - { - attributes: object - } - ] - }); + if (index !== undefined) { + this.props.object[key][index].attributes = object; + const newArray = this.props.object[key].filter((item, itemIndex) => itemIndex !== index); + newArray[index] = { + attributes: object + }; + this.props.onUpdateObject({ + ...this.props.object, + [key]: newArray + }); + } else { + this.props.onUpdateObject({ + ...this.props.object, + [key]: [ + ...this.props.object[key], + { + attributes: object + } + ] + }); + } }; } @@ -214,34 +228,91 @@ export default class ContentTypeForm extends ObjectDetails { return this.setIsVisible(key, false); } - modal(key: string, title: string): Element<*> { - const schema = { - "type": "object", - "required": [ - "title" - ], - "properties": { - "title": { - "type": "string", - "minLength": 1 - }, - "slug": { - "type": "string", - "minLength": 1 - }, - "custom-properties": { - "title": "Custom Properties can be added to this section", - "type": "boolean" + formData(key: string) { + const schemes = { + tabs: { + fieldsToRender: ['title'], + schema: { + "type": "object", + "required": [ + "title" + ], + "properties": { + "title": { + "type": "string", + "minLength": 1 + }, + "slug": { + "type": "string", + "minLength": 1 + }, + "custom-properties": { + "title": "Custom Properties can be added to this section", + "type": "boolean" + } + } + } + }, + sections: { + fieldsToRender: ['title', 'slug', 'custom-properties'], + schema: { + "type": "object", + "required": [ + "title" + ], + "properties": { + "title": { + "type": "string", + "minLength": 1 + }, + "slug": { + "type": "string", + "minLength": 1 + }, + "custom-properties": { + "title": "Custom Properties can be added to this section", + "type": "boolean" + } + } + } + }, + properties: { + fieldsToRender: ['title', 'slug'], + schema: { + "type": "object", + "required": [ + "title" + ], + "properties": { + "title": { + "type": "string", + "minLength": 1 + }, + "slug": { + "type": "string", + "minLength": 1 + }, + "custom-properties": { + "title": "Custom Properties can be added to this section", + "type": "boolean" + } + } } } }; + return _.get(schemes, key, {}); + } + + modal({ key, title, index = 0 }): Element<*> { + const formData = this.formData(key); + return ( { - const onAdd = this.setIsVisible(key, true); - - const footer = ( -
- -
- ); + form({ key, index = 0 }): Element<*> { + const formData = this.formData(key); - const isEmpty = !children; + if (!this.state[key].showModal) return null; - const emptyBody = !isEmpty ? null : ( - - {`Add a ${title.toLowerCase()}`} - + return ( +
); + } + + + column({ key, title, children, footer, emptyBody }): Element<*> { + const isEmpty = _.isEmpty(children); const bodyClassName = classNames( styles['column-body'], @@ -282,12 +351,16 @@ export default class ContentTypeForm extends ObjectDetails { bodyClassName={bodyClassName} title={title} actionBlock={this.actions} - footer={footer} + footer={( +
+ {footer} +
+ )} indentContent={false} > {children} - {emptyBody} - {this.modal(key, title)} + {isEmpty && emptyBody} + {key === 'properties' ? null : this.modal({ key, title })} ); } @@ -295,10 +368,99 @@ export default class ContentTypeForm extends ObjectDetails { renderColumns(): Element<*> { return (
- {this.column('tabs', 'Tab', _.map(this.props.object.tabs, (tab) => ))} - {this.column('sections', 'Section', _.map(this.props.object.sections, (section) => ))} - {this.column('properties', 'Properties')} - {this.column('property-settings', 'Property Settings')} + {this.column( + { + key: 'tabs', + title: 'Tab', + emptyBody: ( + + Add a tab! + + ), + footer: ( + + ), + children: _.map(this.props.object.tabs, (tab) => ) + } + )} + {this.column( + { + key: 'sections', + title: 'Section', + emptyBody: ( + + Add a section! + + ), + footer: ( + + ), + children: _.map(this.props.object.sections, (section) => ) + } + )} + {this.column( + { + key: 'properties', + title: 'Properties', + emptyBody: ( + + Add a property! + + ), + footer: ( + + ), + children: _.map(this.props.object.properties, (property, index) => ( + + )) + } + )} + {this.column( + { + key: 'properties', + title: 'Property Settings', + footer: this.state.properties.showModal ? ( + + ) : null, + children: this.form( + { + key: 'properties', + index: this.state.currentPropertyIndex + } + ) + } + )}
); } From 1224ac08efbe54a6c3ca7ddb5e461f8488df9229 Mon Sep 17 00:00:00 2001 From: Eugene Glova Date: Mon, 3 Jul 2017 16:32:12 +0300 Subject: [PATCH 09/12] Add form --- ashes/src/components/content-types/form.js | 69 ++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 ashes/src/components/content-types/form.js diff --git a/ashes/src/components/content-types/form.js b/ashes/src/components/content-types/form.js new file mode 100644 index 0000000000..dbfcf9b120 --- /dev/null +++ b/ashes/src/components/content-types/form.js @@ -0,0 +1,69 @@ +/* @flow */ + +// libs +import React, { Component, Element } from 'react'; +import { autobind } from 'core-decorators'; + +// components +import ObjectFormInner from 'components/object-form/object-form-inner'; +import SaveCancel from 'components/core/save-cancel'; + +type Props = { + title: string, + isVisible: boolean, + schema: object, + object: object, + fieldsToRender: Array, + onCancel: Function, + onUpdateObject: Function, +}; + +type State = { + object: object, +}; + +export default class MyForm extends Component { + props: Props; + + state: State = { + object: {}, + }; + + @autobind + handleChange(object: object) { + this.setState({ object }); + } + + @autobind + handleSave() { + this.props.onSave(this.state.object); + this.props.onCancel(); + } + + get footer() { + const saveDisabled = false; + + return ; + } + + render() { + const props = this.props; + + const attributes = { + ...props.object, + ...this.state.object, + }; + + return ( +
+ + {this.footer} +
+ ); + } +} From c83f3f043a68ae5a2a3637dca239f9d297058b9f Mon Sep 17 00:00:00 2001 From: Eugene Glova Date: Mon, 3 Jul 2017 16:51:13 +0300 Subject: [PATCH 10/12] Add ability to delete property settings --- .../content-types/content-type-form.jsx | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/ashes/src/components/content-types/content-type-form.jsx b/ashes/src/components/content-types/content-type-form.jsx index 9c11d5a528..65033cb1b8 100644 --- a/ashes/src/components/content-types/content-type-form.jsx +++ b/ashes/src/components/content-types/content-type-form.jsx @@ -200,7 +200,6 @@ export default class ContentTypeForm extends ObjectDetails { onSave(key, index) { return object => { if (index !== undefined) { - this.props.object[key][index].attributes = object; const newArray = this.props.object[key].filter((item, itemIndex) => itemIndex !== index); newArray[index] = { attributes: object @@ -223,6 +222,17 @@ export default class ContentTypeForm extends ObjectDetails { }; } + @autobind + onDelete(key, index) { + return () => { + const newArray = this.props.object[key].filter((item, itemIndex) => itemIndex !== index); + this.props.onUpdateObject({ + ...this.props.object, + [key]: newArray + }); + }; + } + @autobind onCancel(key) { return this.setIsVisible(key, false); @@ -447,8 +457,10 @@ export default class ContentTypeForm extends ObjectDetails { title: 'Property Settings', footer: this.state.properties.showModal ? ( From d3c5b351bd039c9915f7795d1ae9c5be5ef37b6e Mon Sep 17 00:00:00 2001 From: Eugene Glova Date: Wed, 5 Jul 2017 15:49:01 +0300 Subject: [PATCH 11/12] Reset form and modal after edit --- ashes/src/components/content-types/form.js | 1 + ashes/src/components/content-types/modal.js | 1 + 2 files changed, 2 insertions(+) diff --git a/ashes/src/components/content-types/form.js b/ashes/src/components/content-types/form.js index dbfcf9b120..fd31748806 100644 --- a/ashes/src/components/content-types/form.js +++ b/ashes/src/components/content-types/form.js @@ -38,6 +38,7 @@ export default class MyForm extends Component { handleSave() { this.props.onSave(this.state.object); this.props.onCancel(); + this.setState({ object: {} }); } get footer() { diff --git a/ashes/src/components/content-types/modal.js b/ashes/src/components/content-types/modal.js index a860756f11..5b88ddf184 100644 --- a/ashes/src/components/content-types/modal.js +++ b/ashes/src/components/content-types/modal.js @@ -39,6 +39,7 @@ export default class MyModal extends Component { handleSave() { this.props.onSave(this.state.object); this.props.onCancel(); + this.setState({ object: {} }); } get footer() { From 2b24d0498eec1218dc7cd2d5dc436b9bdc7a9e11 Mon Sep 17 00:00:00 2001 From: Eugene Glova Date: Wed, 5 Jul 2017 15:49:22 +0300 Subject: [PATCH 12/12] Use flat structure like modernizr for nested objects --- .../content-types/content-type-form.jsx | 94 +++++++++--------- ashes/src/paragons/content-type.js | 95 ++++++++++++++++--- 2 files changed, 125 insertions(+), 64 deletions(-) diff --git a/ashes/src/components/content-types/content-type-form.jsx b/ashes/src/components/content-types/content-type-form.jsx index 65033cb1b8..4fd0d3aa7b 100644 --- a/ashes/src/components/content-types/content-type-form.jsx +++ b/ashes/src/components/content-types/content-type-form.jsx @@ -21,7 +21,11 @@ import qualifiers from './qualifiers'; import ContentBox from 'components/content-box/content-box'; import { Button } from 'components/core/button'; -import { setDiscountAttr } from 'paragons/promotion'; +import { + addContentTypeObject, + updateContentTypeObject, + removeContentTypeObject +} from 'paragons/content-type'; import { setObjectAttr, omitObjectAttr } from 'paragons/object'; import { customerGroups } from 'paragons/object-types'; const layout = require('./layout.json'); @@ -34,8 +38,6 @@ export default class ContentTypeForm extends ObjectDetails { tabs: {}, sections: {}, properties: {}, - 'property-settings': {}, - currentPropertyIndex: 0 } renderApplyType() { @@ -186,50 +188,37 @@ export default class ContentTypeForm extends ObjectDetails { @autobind setIsVisible(key, value) { - return () => { + return id => { this.setState({ [key]: { ...this.state[key], - showModal: value + showModal: value, + id, } }); }; } @autobind - onSave(key, index) { - return object => { - if (index !== undefined) { - const newArray = this.props.object[key].filter((item, itemIndex) => itemIndex !== index); - newArray[index] = { - attributes: object - }; - this.props.onUpdateObject({ - ...this.props.object, - [key]: newArray - }); + onSave(key, id) { + return attributes => { + const { object: contentType } = this.props; + if (id > 0) { + this.props.onUpdateObject(updateContentTypeObject(contentType, key, id, attributes)); + return id; } else { - this.props.onUpdateObject({ - ...this.props.object, - [key]: [ - ...this.props.object[key], - { - attributes: object - } - ] - }); + const object = addContentTypeObject(contentType, key, attributes); + this.props.onUpdateObject(object); + return _.last(object[key].allIds); } }; } @autobind - onDelete(key, index) { + onDelete(key, id) { return () => { - const newArray = this.props.object[key].filter((item, itemIndex) => itemIndex !== index); - this.props.onUpdateObject({ - ...this.props.object, - [key]: newArray - }); + const { object: contentType } = this.props; + this.props.onUpdateObject(removeContentTypeObject(contentType, key, id)); }; } @@ -314,34 +303,38 @@ export default class ContentTypeForm extends ObjectDetails { return _.get(schemes, key, {}); } - modal({ key, title, index = 0 }): Element<*> { + modal({ key, title }): Element<*> { const formData = this.formData(key); + const { object: contentType } = this.props; + const { id, showModal } = this.state[key]; return ( ); } - form({ key, index = 0 }): Element<*> { + form({ key }): Element<*> { const formData = this.formData(key); + const { object: contentType } = this.props; + const { id, showModal } = this.state[key]; - if (!this.state[key].showModal) return null; + if (!showModal) return null; return ( ); } @@ -376,6 +369,7 @@ export default class ContentTypeForm extends ObjectDetails { } renderColumns(): Element<*> { + const { object: contentType } = this.props; return (
{this.column( @@ -395,7 +389,7 @@ export default class ContentTypeForm extends ObjectDetails { Tab ), - children: _.map(this.props.object.tabs, (tab) => ) + children: _.map(contentType.tabs.byId, (tab) => ) } )} {this.column( @@ -415,7 +409,12 @@ export default class ContentTypeForm extends ObjectDetails { Section ), - children: _.map(this.props.object.sections, (section) => ) + children: _.map(contentType.sections.byId, (section, id) => ( +
+ {section.attributes.title.v} + +
+ )) } )} {this.column( @@ -431,19 +430,17 @@ export default class ContentTypeForm extends ObjectDetails { ), - children: _.map(this.props.object.properties, (property, index) => ( + children: _.map(this.props.object.properties.byId, (property, id) => (