diff --git a/package.json b/package.json index 5f1ed11..1697c89 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "facebook-instant-articles-sdk-rules-editor", "productName": "Facebook Instant Articles Rules Editor", - "version": "0.0.1", + "version": "0.2.0", "description": "App to help creating transformer rules for the Facebook Instant Articles SDK.", "license": "SEE LICENSE IN ./LICENSE", "main": "src/js/background.js", diff --git a/src/css/style.css b/src/css/style.css index 375188a..6a14952 100644 --- a/src/css/style.css +++ b/src/css/style.css @@ -65,6 +65,7 @@ h1 { padding-right: 10px; overflow-y: auto; display: flex; + flex-direction: column; } .loader { @@ -77,14 +78,19 @@ h1 { } .field-line label, -.field-line input { +.field-line input, +.field-line select { display: block; width: 100%; clear: both; } .field-line input, -.field-line input:active { +.field-line input:active, +.field-line textarea, +.field-line textarea:active, +.field-line select, +.field-line select:active { border-radius: 5px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; @@ -394,7 +400,8 @@ h1 { } .field-line.active, -.field-line input:focus { +.field-line input:focus, +.field-line textarea:focus { border: 1px solid #4a90e2; } @@ -427,7 +434,8 @@ webview { color: #ccc !important; } -.selectors-form { +.selectors-form, +.settings-fields { margin-bottom: 10px; position: relative; background: rgba(255, 255, 255, 0.6); @@ -443,7 +451,8 @@ webview { } .selectors-form .field, .selectors-form .attributes, -.selector-label { +.selector-label, +.settings-fields .field { padding: 0 5px !important; } @@ -476,6 +485,7 @@ webview { color: rgba(0, 0, 0, 0.87); padding: 5px; cursor: pointer; + width: 100%; } .rule-header { @@ -594,7 +604,29 @@ button { .rule-list { display: flex; flex-direction: column; - width: 300px; + flex-grow: 1; + width: 325px; +} + +.rule-list label { + display: block; +} + +.settings { + width: 100%; +} + +.settings input, +.settings textarea, +.settings select { + border-radius: 5px; + border: 1px solid #ccc; +} + +.settings textarea, +.settings select { + margin: 5px 0; + width: 100%; } .scrollable { @@ -613,7 +645,6 @@ button { margin: 0; display: flex; flex-direction: column; - margin-right: 5px; } .hidden { diff --git a/src/js/components/BugReporter.react.js b/src/js/components/BugReporter.react.js index 6258341..b6a14c5 100644 --- a/src/js/components/BugReporter.react.js +++ b/src/js/components/BugReporter.react.js @@ -44,7 +44,11 @@ export class BugReporter extends React.Component { **URL**: \`${this.props.editor.url}\` **RULES**: \`\`\`json -${JSON.stringify(RuleExporter.export(this.props.rules), null, 2)} +${JSON.stringify( + RuleExporter.export(this.props.rules, this.props.settings), + null, + 2 + )} \`\`\``; //---------------- // End of template diff --git a/src/js/components/FileTools.react.js b/src/js/components/FileTools.react.js index b8af1d2..f3f83f4 100644 --- a/src/js/components/FileTools.react.js +++ b/src/js/components/FileTools.react.js @@ -48,7 +48,7 @@ class FileTools extends React.Component { fileName => { if (fileName) { const contents = JSON.stringify( - RuleExporter.export(this.props.rules) + RuleExporter.export(this.props.rules, this.props.settings) ); Fs.writeFile(fileName, contents, importExportEncoding, error => { if (error) { diff --git a/src/js/components/Preview.react.js b/src/js/components/Preview.react.js index 3942fa6..f10e633 100644 --- a/src/js/components/Preview.react.js +++ b/src/js/components/Preview.react.js @@ -63,7 +63,9 @@ class Preview extends React.Component { bytes: Buffer.from( 'rules=' + encodeURIComponent( - JSON.stringify(RuleExporter.export(this.props.rules)) + JSON.stringify( + RuleExporter.export(this.props.rules, this.props.settings) + ) ) ), }, @@ -86,7 +88,9 @@ class Preview extends React.Component { bytes: Buffer.from( 'rules=' + encodeURIComponent( - JSON.stringify(RuleExporter.export(this.props.rules)) + JSON.stringify( + RuleExporter.export(this.props.rules, this.props.settings) + ) ) ), }, @@ -102,8 +106,8 @@ class Preview extends React.Component { return true; } if ( - RuleExporter.export(nextProps.rules) != - RuleExporter.export(this.props.rules) + RuleExporter.export(nextProps.rules, nextProps.settings) != + RuleExporter.export(this.props.rules, this.props.settings) ) { return true; } diff --git a/src/js/components/RuleList.react.js b/src/js/components/RuleList.react.js index 9f18d26..67241ea 100644 --- a/src/js/components/RuleList.react.js +++ b/src/js/components/RuleList.react.js @@ -11,10 +11,11 @@ const React = require('react'); const RulePicker = require('./RulePicker.react.js'); const RuleActions = require('../data/RuleActions'); +const TransformationSettings = require('./TransformationSettings.react.js'); import { Set } from 'immutable'; import RuleCategories from '../models/RuleCategories'; -import { Dropdown, Icon } from 'semantic-ui-react'; +import { Accordion, Dropdown, Icon } from 'semantic-ui-react'; import { RuleFactory } from '../models/Rule'; import type { Props } from '../containers/AppContainer.react'; import { SortableContainer, SortableElement } from 'react-sortable-hoc'; @@ -57,7 +58,20 @@ const SortableList = SortableContainer((props: any) => { ); }); -class RuleList extends React.Component { +type AccordionItemProps = { + index: number +}; + +type State = { + activeAccordionIndex: number +}; + +class RuleList extends React.Component { + constructor(props: Props) { + super(props); + this.state = { activeAccordionIndex: 0 }; + } + handleAddRule = (event: Event) => { const selectElement = event.target; if (selectElement instanceof HTMLSelectElement) { @@ -100,79 +114,105 @@ class RuleList extends React.Component { } } + handleAccordionTitleClick = (e: Event, itemProps: AccordionItemProps) => { + const { index } = itemProps; + const { activeAccordionIndex } = this.state; + const newIndex = activeAccordionIndex === index ? -1 : index; + + this.setState({ activeAccordionIndex: newIndex }); + }; + render() { return (
-
- - ({ - text: category, - value: category, - icon: getLabelIcon(category), - }) - )} - renderLabel={item => ({ - content: item.text, - icon: item.icon, - })} - text="Pick at least 1 category" - value={this.props.editor.categories.toArray()} - onChange={this.handleChangeFilters} - /> -
-
- - -
- + + + + +
+ + ({ + text: category, + value: category, + icon: getLabelIcon(category), + }) + )} + renderLabel={item => ({ + content: item.text, + icon: item.icon, + })} + text="Pick at least 1 category" + value={this.props.editor.categories.toArray()} + onChange={this.handleChangeFilters} + /> +
+ + +
+ +
+
+ -
+
); } diff --git a/src/js/components/TransformationSettings.react.js b/src/js/components/TransformationSettings.react.js new file mode 100644 index 0000000..dd3d78b --- /dev/null +++ b/src/js/components/TransformationSettings.react.js @@ -0,0 +1,202 @@ +/** + * Copyright 2017-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +const React = require('react'); + +import { Accordion, Icon } from 'semantic-ui-react'; +import AdsTypes from '../data/AdsTypes'; +import SettingsActions from '../data/SettingsActions'; + +import type { Props as BaseProps } from '../containers/AppContainer.react'; + +type Props = BaseProps & { + accordionActive: boolean, + accordionIndex: number, + onAccordionTitleClick: (e: Event, itemProps: AccordionItemProps) => void +}; + +type AccordionItemProps = { + index: number +}; + +class TransformationSettings extends React.Component { + handleAdsRawHtmlChanged = (event: SyntheticEvent) => { + SettingsActions.editAdsRawHtml(event.currentTarget.value); + }; + + handleAdsTypeChanged = (event: SyntheticEvent) => { + SettingsActions.editAdsType(event.currentTarget.value); + }; + + handleAnalyticsRawHtmlChanged = (event: SyntheticEvent) => { + SettingsActions.editAnalyticsRawHtml(event.currentTarget.value); + }; + + handleAudienceNetworkPlacementIdChanged = ( + event: SyntheticEvent + ) => { + SettingsActions.editAudienceNetworkPlacementId(event.currentTarget.value); + }; + + handleFbPixelIdChanged = (event: SyntheticEvent) => { + SettingsActions.editFbPixelId(event.currentTarget.value); + }; + + handleStyleNameChanged = (event: SyntheticEvent) => { + SettingsActions.editStyleName(event.currentTarget.value); + }; + + renderAudienceNetworkDiv() { + return this.props.settings.adsSettings.type === + AdsTypes.AUDIENCE_NETWORK ? ( +
+ +
+ +
+
+ ) : null; + } + + renderAdsRawHtmlDiv() { + return this.props.settings.adsSettings.type === AdsTypes.RAW_HTML ? ( +
+ +
+