From 278d80185e4cc11923ea2da1e8470f566ff3b66d Mon Sep 17 00:00:00 2001 From: Julie Pan Date: Wed, 5 Jul 2017 17:20:09 -0400 Subject: [PATCH 1/8] cleanups --- js/app.js | 9 +- js/components/Lists/actions.js | 16 +- js/components/UserProfile/BasicSettings.jsx | 60 ++++++++ manifest.json | 2 +- package.json | 7 +- serviceworker.js | 155 ++++++++++++-------- 6 files changed, 171 insertions(+), 78 deletions(-) diff --git a/js/app.js b/js/app.js index ed9ab3c5..0481d933 100644 --- a/js/app.js +++ b/js/app.js @@ -13,11 +13,14 @@ // import 'file-loader?name=[name].[ext]!../.htaccess'; // Check for ServiceWorker support before trying to install it -// if (process.env.NODE_ENV === 'production') { -// if ('serviceWorker' in navigator) { +// if (process.env.NODE_ENV === 'development') { +// if ('serviceWorker' in navigator && 'PushManager' in window) { // navigator.serviceWorker.register('/serviceworker.js') // .then( -// registration => console.log('ServiceWorker registration successful with scope: ', registration.scope), +// registration => { +// console.log('ServiceWorker registration successful with scope: ', registration.scope); +// window.swRegistration = registration; +// }, // err => console.log('ServiceWorker registration failed: ', err) // ).catch(err => { // // Registration failed diff --git a/js/components/Lists/actions.js b/js/components/Lists/actions.js index 5fccfcec..9d620248 100644 --- a/js/components/Lists/actions.js +++ b/js/components/Lists/actions.js @@ -111,9 +111,7 @@ export function fetchLists() { dispatch(requestLists()); return api.get(`/lists?limit=${PAGE_LIMIT}&offset=${OFFSET}&order=-Created`) .then(response => { - const res = normalize(response, { - data: arrayOf(listSchema), - }); + const res = normalize(response, {data: arrayOf(listSchema)}); const newOffset = response.data.length < PAGE_LIMIT ? null : OFFSET + PAGE_LIMIT; return dispatch(receiveLists(res.entities.lists, res.result.data, newOffset)); }) @@ -129,9 +127,7 @@ export function fetchPublicLists() { dispatch(requestLists()); return api.get(`/lists/public?limit=${PAGE_LIMIT}&offset=${OFFSET}`) .then(response => { - const res = normalize(response, { - data: arrayOf(listSchema), - }); + const res = normalize(response, {data: arrayOf(listSchema)}); const newOffset = response.data.length < PAGE_LIMIT ? null : OFFSET + PAGE_LIMIT; return dispatch({ type: listConstant.RECEIVE_MULTIPLE, @@ -175,9 +171,7 @@ export function fetchTagLists(tagQuery) { dispatch(requestLists()); return api.get(`/lists?q=tag:${tagQuery}&limit=${PAGE_LIMIT}&offset=${OFFSET}`) .then(response => { - const res = normalize(response, { - data: arrayOf(listSchema), - }); + const res = normalize(response, {data: arrayOf(listSchema)}); const newOffset = response.data.length < PAGE_LIMIT ? null : OFFSET + PAGE_LIMIT; return dispatch({ type: listConstant.RECEIVE_MULTIPLE, @@ -200,9 +194,7 @@ export function fetchArchivedLists() { dispatch(requestLists()); return api.get(`/lists/archived?limit=${PAGE_LIMIT}&offset=${OFFSET}&order=-Created`) .then(response => { - const res = normalize(response, { - data: arrayOf(listSchema), - }); + const res = normalize(response, {data: arrayOf(listSchema)}); const newOffset = response.data.length < PAGE_LIMIT ? null : OFFSET + PAGE_LIMIT; return dispatch({ type: listConstant.RECEIVE_MULTIPLE, diff --git a/js/components/UserProfile/BasicSettings.jsx b/js/components/UserProfile/BasicSettings.jsx index 8cb3ac8a..b5e4280f 100644 --- a/js/components/UserProfile/BasicSettings.jsx +++ b/js/components/UserProfile/BasicSettings.jsx @@ -4,6 +4,7 @@ import {ToggleableEditInputHOC, ToggleableEditInput} from '../ToggleableEditInpu import {fromJS, is} from 'immutable'; import {grey500} from 'material-ui/styles/colors'; import RaisedButton from 'material-ui/RaisedButton'; +import Toggle from 'material-ui/Toggle'; import {actions as loginActions} from 'components/Login'; @@ -43,9 +44,26 @@ class BasicSettings extends Component { this.state = { immuperson: fromJS(this.props.person), newPerson: fromJS(this.props.person), + notifySubscribed: false }; + // this.onToggle = this.onToggle.bind(this); + // this.onSubscribe = this.onSubscribe.bind(this); + // this.onUnsubscribe = this.onUnsubscribe.bind(this); } + // componentWillMount() { + // navigator.serviceWorker.ready.then(swRegistration => { + // swRegistration.pushManager.getSubscription() + // .then(subscription => { + // const isSubscribed = !(subscription === null); + // console.log('subscription'); + // console.log(isSubscribed); + // this.setState({notifySubscribed: isSubscribed}); + // }); + // window.swRegistration = swRegistration; + // }); + // } + componentWillUnmount() { if (!is(this.state.immuperson, this.state.newPerson)) { const newPerson = this.state.newPerson; @@ -59,6 +77,38 @@ class BasicSettings extends Component { } } + // onToggle(e, isToggled) { + // if (isToggled) this.onSubscribe(); + // else this.onUnsubscribe(); + // } + + // onSubscribe() { + // window.swRegistration.pushManager + // .subscribe({userVisibleOnly: true}) + // .then(subscription => { + // console.log(subscription); + // this.setState({notifySubscribed: true}); + // }) + // .catch(e => { + // console.log('Push Notify subscription denied by user'); + // }); + // } + + // onUnsubscribe() { + // window.swRegistration.pushManager.getSubscription() + // .then(subscription => { + // if (!subscription) { + // this.setState({notifySubscribed: false}); + // } + // subscription.unsubscribe() + // .then(_ => this.setState({notifySubscribed: false})); + // }) + // .then(e => { + // console.log('failed to subscribe'); + // }); + // } + + render() { const {person} = this.props; const state = this.state; @@ -105,6 +155,16 @@ class BasicSettings extends Component { />} + {/* +
+
+ Browser Notifications +
+
+ +
+
+ */} ); diff --git a/manifest.json b/manifest.json index 6891b967..dfd0284f 100644 --- a/manifest.json +++ b/manifest.json @@ -30,5 +30,5 @@ "start_url": "index.html", "display": "standalone", "orientation": "portrait", - "background_color": "#FFFFFF" + "background_color": "#FFFFFF", } \ No newline at end of file diff --git a/package.json b/package.json index 1cc85397..ec3f6b78 100644 --- a/package.json +++ b/package.json @@ -6,13 +6,14 @@ "alertifyjs": "^1.10.0", "axios": "^0.9.1", "classnames": "^2.2.5", - "draft-convert": "^1.4.3", + "draft-convert": "^1.4.7", "draft-js": "^0.10.0", "es6-promise": "^4.0.5", "fontfaceobserver": "^1.5.1", "fuse.js": "^2.6.2", "fuzzy": "^0.1.1", "hopscotch": "^0.2.6", + "ifvisible.js": "^1.0.6", "immutability-helper": "^2.2.2", "immutable": "^3.8.1", "install": "^0.8.1", @@ -25,7 +26,6 @@ "moment": "^2.14.1", "moment-timezone": "^0.5.9", "normalizr": "^2.2.1", - "npm": "^3.10.6", "numbro": "^1.9.3", "object-assign": "^4.1.0", "pikaday": "^1.4.0", @@ -70,8 +70,7 @@ "regression": "^1.2.1", "sanitize-html": "^1.14.1", "tlds": "^1.157.0", - "validator": "^5.5.0", - "zeroclipboard": "^2.2.0" + "validator": "^5.5.0" }, "devDependencies": { "appcache-webpack-plugin": "^1.2.0", diff --git a/serviceworker.js b/serviceworker.js index a811bbee..9c86de4f 100644 --- a/serviceworker.js +++ b/serviceworker.js @@ -1,61 +1,100 @@ -var CACHE_NAME = 'react-boilerplate-cache-v1'; -// The files we want to cache -var urlsToCache = [ - // '/', - '/css/main.css' -// '/js/bundle.js' -]; - -// Set the callback for the install step -self.addEventListener('install', function(event) { - // Perform install steps +self.addEventListener('push', function(event) { + console.log('Received a push message', event); + + // var title = 'Yay a message.'; + // var body = 'We have received a push message.'; + // var tag = 'simple-push-demo-notification-tag'; + // event.waitUntil( - caches.open(CACHE_NAME) - .then(function(cache) { - console.log('Opened cache'); - return cache.addAll(urlsToCache); - }); -}); + // self.registration.showNotification(title, { + // body: body, + // tag: tag + // }) + // ); + function log(argument) { + console.log(argument); + } + + function notification(args) { + var notifications = JSON.parse(args.data); + for (var i = notifications.length - 1; i >= 0; i--) { + self.registration.showNotification('Tabulae Notification', { + body: notifications[i].message, + }); + } + } -// Set the callback when the files get fetched -self.addEventListener('fetch', function(event) { - event.respondWith( - caches.match(event.request) - .then(function(response) { - // Cached files available, return those - if (response) { - return response; - } - - // IMPORTANT: Clone the request. A request is a stream and - // can only be consumed once. Since we are consuming this - // once by cache and once by the browser for fetch, we need - // to clone the response - var fetchRequest = event.request.clone(); - - // Start request again since there are no files in the cache - return fetch(fetchRequest).then(function(response) { - // If response is invalid, throw error - if (!response || response.status !== 200 || response.type !== 'basic') { - return response; - } - - // IMPORTANT: Clone the response. A response is a stream - // and because we want the browser to consume the response - // as well as the cache consuming the response, we need - // to clone it so we have 2 stream. - var responseToCache = response.clone(); - - // Otherwise cache the downloaded files - caches.open(CACHE_NAME) - .then(function(cache) { - cache.put(event.request, responseToCache); - }); - - // And return the network response - return response; - } - ); - }) - ); + event.waitUntil( + fetch('/users/me/token') + .then(response => { + const channel = new goog.appengine.Channel(response.token); + const socket = channel.open(); + socket.onopen = log; + socket.onmessage = args => notification(args); + socket.onerror = log; + socket.onclose = log; + })); }); + + +// var CACHE_NAME = 'react-boilerplate-cache-v1'; +// // The files we want to cache +// var urlsToCache = [ +// // '/', +// '/css/main.css' +// // '/js/bundle.js' +// ]; + +// // Set the callback for the install step +// self.addEventListener('install', function(event) { +// // Perform install steps +// // event.waitUntil( +// caches.open(CACHE_NAME) +// .then(function(cache) { +// console.log('Opened cache'); +// return cache.addAll(urlsToCache); +// }); +// }); + +// // Set the callback when the files get fetched +// self.addEventListener('fetch', function(event) { +// event.respondWith( +// caches.match(event.request) +// .then(function(response) { +// // Cached files available, return those +// if (response) { +// return response; +// } + +// // IMPORTANT: Clone the request. A request is a stream and +// // can only be consumed once. Since we are consuming this +// // once by cache and once by the browser for fetch, we need +// // to clone the response +// var fetchRequest = event.request.clone(); + +// // Start request again since there are no files in the cache +// return fetch(fetchRequest).then(function(response) { +// // If response is invalid, throw error +// if (!response || response.status !== 200 || response.type !== 'basic') { +// return response; +// } + +// // IMPORTANT: Clone the response. A response is a stream +// // and because we want the browser to consume the response +// // as well as the cache consuming the response, we need +// // to clone it so we have 2 stream. +// var responseToCache = response.clone(); + +// // Otherwise cache the downloaded files +// caches.open(CACHE_NAME) +// .then(function(cache) { +// cache.put(event.request, responseToCache); +// }); + +// // And return the network response +// return response; +// } +// ); +// }) +// ); +// }); From b2952143a23ff74412b65d27885146064af83b5a Mon Sep 17 00:00:00 2001 From: Julie Pan Date: Mon, 10 Jul 2017 15:44:57 -0400 Subject: [PATCH 2/8] fix undefined error in sentry TABULAE-SITE-3V ASSIGNED --- index.html | 2 +- js/components/Email/EmailPanel/EmailPanel.jsx | 52 +++++++++++++++++-- .../ListTable/CopyToHOC/CopyToHOC.jsx | 8 +-- 3 files changed, 54 insertions(+), 8 deletions(-) diff --git a/index.html b/index.html index 12fd8eb3..760c0258 100644 --- a/index.html +++ b/index.html @@ -5,7 +5,7 @@ - + diff --git a/js/components/Email/EmailPanel/EmailPanel.jsx b/js/components/Email/EmailPanel/EmailPanel.jsx index 127592e6..e2a1e7c6 100644 --- a/js/components/Email/EmailPanel/EmailPanel.jsx +++ b/js/components/Email/EmailPanel/EmailPanel.jsx @@ -20,7 +20,6 @@ import isJSON from 'validator/lib/isJSON'; import Select from 'react-select'; -import VirtualizedSelect from 'react-virtualized-select'; import ReactTooltip from 'react-tooltip' import RaisedButton from 'material-ui/RaisedButton'; import FlatButton from 'material-ui/FlatButton'; @@ -43,7 +42,6 @@ import PauseOverlay from './PauseOverlay.jsx'; import 'react-select/dist/react-select.css'; import 'react-virtualized/styles.css'; -import 'react-virtualized-select/styles.css'; import './react-select-hack.css'; import 'node_modules/alertifyjs/build/css/alertify.min.css'; import './ReactTagsStyle.css'; @@ -127,6 +125,7 @@ class EmailPanel extends Component { this.onClearClick = this._onClearClick.bind(this); this.checkEmailDupes = this._checkEmailDupes.bind(this); this.changeEmailSignature = this._changeEmailSignature.bind(this); + this.onSendTestEmail = this.onSendTestEmail.bind(this); // cleanups this.onEmailSendClick = _ => this.checkEmailDupes().then(this.onPreviewEmailsClick); @@ -338,7 +337,6 @@ class EmailPanel extends Component { else invalidEmailContacts.push(contact); }); const {contactEmails, emptyFields} = this.getGeneratedHtmlEmails(validEmailContacts, subject, body); - console.log(emptyFields); Promise.resolve() .then(_ => @@ -405,7 +403,47 @@ class EmailPanel extends Component { .catch(_ => { console.log('CANCELLED'); }); + } + onSendTestEmail() { + // const {subject, body} = this.state; + // const email = this.props.person.email; + // let newHtml = html; + + // this.state.fieldsmap.map(fieldObj => { + // let value = ''; + // const replaceValue = _getter(contact, fieldObj); + // if (replaceValue) value = replaceValue; + // const regexValue = new RegExp('\{' + fieldObj.name + '\}', 'g'); + // // count num custom vars used + // const matches = newHtml.match(regexValue); + // if (matches !== null) { + // if (!value) emptyFields.push(fieldObj.name); + // matchCount[fieldObj.name] = matches.length; + // } + // newHtml = newHtml.replace(regexValue, value); + // if (expectedMatches !== null) expectedMatches = expectedMatches.filter(match => match !== `{${fieldObj.name}}`); + // }); + + // const bodyObj = replaceAll(body, selectedContacts[i], this.state.fieldsmap); + // const subjectObj = replaceAll(subject, selectedContacts[i], this.state.fieldsmap); + // let emailObj = { + // listid: this.props.listId, + // to: contact.email, + // subject: subjectObj.html, + // body: bodyObj.html, + // contactid: contact.id, + // templateid: this.state.currentTemplateId, + // cc: this.props.cc.map(item => item.text), + // bcc: this.props.bcc.map(item => item.text), + // fromemail: this.props.from, + // }; + // if (this.props.scheduledtime !== null) { + // emailObj.sendat = this.props.scheduledtime; + // } + // if (subjectObj.numMatches > 0) { + // emailObj.baseSubject = subject; + // } } _onClearClick() { @@ -473,7 +511,13 @@ class EmailPanel extends Component { {props.isImageReceiving && } -
+
+ { + /* + + + */ + } none selected} {props.selectedContacts && - {props.selectedContacts - .map(contact => contact.firstname || contact.lastname || contact.email) - .join(', ')}} + { + props.selectedContacts + .map(contact => contact.firstname || contact.lastname || contact.email || contact.id) + .join(', ') + }}

Select the List(s) to Copy these selected contacts to:

From b5895e3a06bc8674aca54ee8c61cafee3652646b Mon Sep 17 00:00:00 2001 From: Julie Pan Date: Mon, 10 Jul 2017 16:13:59 -0400 Subject: [PATCH 3/8] cleanups + add track event when use prev email template --- js/components/Email/EmailPanel/EmailPanel.jsx | 4 + .../ColumnEditPanelHOC/ColumnEditPanelHOC.jsx | 168 +++++++++--------- 2 files changed, 86 insertions(+), 86 deletions(-) diff --git a/js/components/Email/EmailPanel/EmailPanel.jsx b/js/components/Email/EmailPanel/EmailPanel.jsx index e2a1e7c6..296c66ba 100644 --- a/js/components/Email/EmailPanel/EmailPanel.jsx +++ b/js/components/Email/EmailPanel/EmailPanel.jsx @@ -225,6 +225,10 @@ class EmailPanel extends Component { this.setState({bodyEditorState: templateJSON.data}); this.props.saveEditorState(templateJSON.data); this.setState({subjectHtml}); + if (templateJSON.date) { + window.Intercom('trackEvent', 'use_prev_email_template', {date: templateJSON.date}); + mixpanel.track('use_prev_email_template', {date: templateJSON.date}); + } } else { this.props.setBodyHtml(bodyHtml); this.setState({bodyHtml, subjectHtml}); diff --git a/js/components/ListTable/ColumnEditPanelHOC/ColumnEditPanelHOC.jsx b/js/components/ListTable/ColumnEditPanelHOC/ColumnEditPanelHOC.jsx index 8e21c61d..b94ff7d7 100644 --- a/js/components/ListTable/ColumnEditPanelHOC/ColumnEditPanelHOC.jsx +++ b/js/components/ListTable/ColumnEditPanelHOC/ColumnEditPanelHOC.jsx @@ -6,17 +6,8 @@ import Container from './Container.jsx'; import {connect} from 'react-redux'; import Dialog from 'material-ui/Dialog'; import FlatButton from 'material-ui/FlatButton'; -import RaisedButton from 'material-ui/RaisedButton'; -import {yellow50} from 'material-ui/styles/colors'; - -import { - generateTableFieldsmap, - measureSpanSize, - exportOperations, - isNumber, - _getter, - reformatFieldsmap -} from 'components/ListTable/helpers'; +import {yellow50, grey600} from 'material-ui/styles/colors'; +import {generateTableFieldsmap, reformatFieldsmap} from 'components/ListTable/helpers'; import alertify from 'alertifyjs'; alertify.promisifyConfirm = (title, description) => new Promise((resolve, reject) => { @@ -28,24 +19,22 @@ alertify.promisifyPrompt = (title, description, defaultValue) => new Promise((re }); class ColumnEditPanelHOC extends Component { - constructor(props) { - super(props); - const hiddenList = this.props.fieldsmap.filter(field => field.hidden && !field.tableOnly); - const showList = this.props.fieldsmap.filter(field => !field.hidden && !field.tableOnly); - this.state = { - hiddenList, - showList, - open: false, - isUpdating: false, - dirty: false, - }; - this.updateList = this.updateList.bind(this); + constructor(props) { + super(props); + const hiddenList = this.props.fieldsmap.filter(field => field.hidden && !field.tableOnly); + const showList = this.props.fieldsmap.filter(field => !field.hidden && !field.tableOnly); + this.state = { + hiddenList, + showList, + open: false, + isUpdating: false, + dirty: false, + }; + this.updateList = this.updateList.bind(this); this.onSubmit = this.onSubmit.bind(this); - } - - updateList(list, containerType) { - this.setState({[containerType]: list, dirty: true}); - } + this.onRequestOpen = _ => this.setState({open: true}); + this.onRequestClose = _ => this.setState({open: false}); + } onSubmit() { const fieldsmap = reformatFieldsmap([...this.state.showList, ...this.state.hiddenList]); @@ -56,76 +45,83 @@ class ColumnEditPanelHOC extends Component { }; this.setState({isUpdating: true}); this.props.patchList(listBody) - .then(_ => this.setState({isUpdating: false, open: false})) + .then(_ => this.setState({isUpdating: false, open: false})); + } + + updateList(list, containerType) { + this.setState({[containerType]: list, dirty: true}); } - render() { - const state = this.state; + render() { + const state = this.state; const actions = [ - this.setState({open: false})} - />, - , + , + , ]; - // console.log(this.props.fieldsmap); + // console.log(this.props.fieldsmap); - return ( -
- this.setState({open: false})}> -
+ return ( +
+ +
- Drag each card to reorder the order of you columns. Drag column cards from Hidden Columns to Showing Columns to activate or de-activate default columns. You can also create custom columns that you can use as template variable in emails. + Drag each card to reorder the order of you columns. + Drag column cards from Hidden Columns to Showing Columns to activate or de-activate default columns. + You can also create custom columns that you can use as template variable in emails.
- -
+
There is a number of auto-generated columns that are activated when certain columns are not hidden. For example, activating Instagram Likes and Instagram Comments also activates Likes-to-Comments ratio.
-
- - -
+ {/* +
+ Apply Presets + Use properties from a previously created list +
+ */} +
+ + +
- {this.props.children({onRequestOpen: _ => this.setState({open: true})})} -
- ); - } + {this.props.children({onRequestOpen: this.onRequestOpen})} +
+ ); + } } -const style = { - // display: 'flex', - // justifyContent: 'space-around', - paddingTop: '20px' +const styles = { + instructionContainer: {margin: '20px 0'}, + columnsContainer: {paddingTop: 20}, + panel: { + backgroundColor: yellow50, + margin: 10, + padding: 10 + }, }; const mapStateToProps = (state, props) => { @@ -134,15 +130,15 @@ const mapStateToProps = (state, props) => { const rawFieldsmap = generateTableFieldsmap(list); return { - fieldsmap: rawFieldsmap, - list: list - } + fieldsmap: rawFieldsmap, + list: list + }; }; const mapDispatchToProps = (dispatch, props) => { - return { + return { patchList: listObj => dispatch(listActions.patchList(listObj)), - }; + }; }; export default connect(mapStateToProps, mapDispatchToProps)(DragDropContext(HTML5Backend)(ColumnEditPanelHOC)); From 33a3f2ef1a69a706fa488eb20ff2ced8b7c713fc Mon Sep 17 00:00:00 2001 From: Julie Pan Date: Mon, 10 Jul 2017 17:10:07 -0400 Subject: [PATCH 4/8] add select prev list preset. about #880 --- .../ColumnEditPanelHOC/ColumnEditPanelHOC.jsx | 53 +++++++++++++------ .../ColumnEditPanelHOC/Container.jsx | 10 +++- js/components/ListTable/ListTable.jsx | 31 ++++++----- 3 files changed, 59 insertions(+), 35 deletions(-) diff --git a/js/components/ListTable/ColumnEditPanelHOC/ColumnEditPanelHOC.jsx b/js/components/ListTable/ColumnEditPanelHOC/ColumnEditPanelHOC.jsx index b94ff7d7..128b3b15 100644 --- a/js/components/ListTable/ColumnEditPanelHOC/ColumnEditPanelHOC.jsx +++ b/js/components/ListTable/ColumnEditPanelHOC/ColumnEditPanelHOC.jsx @@ -8,7 +8,9 @@ import Dialog from 'material-ui/Dialog'; import FlatButton from 'material-ui/FlatButton'; import {yellow50, grey600} from 'material-ui/styles/colors'; import {generateTableFieldsmap, reformatFieldsmap} from 'components/ListTable/helpers'; +import Select from 'react-select'; import alertify from 'alertifyjs'; +import 'react-select/dist/react-select.css'; alertify.promisifyConfirm = (title, description) => new Promise((resolve, reject) => { alertify.confirm(title, description, resolve, reject); @@ -26,14 +28,18 @@ class ColumnEditPanelHOC extends Component { this.state = { hiddenList, showList, - open: false, isUpdating: false, dirty: false, }; - this.updateList = this.updateList.bind(this); + this.onUpdateList = this.onUpdateList.bind(this); this.onSubmit = this.onSubmit.bind(this); - this.onRequestOpen = _ => this.setState({open: true}); - this.onRequestClose = _ => this.setState({open: false}); + this.onListPresetSelect = this.onListPresetSelect.bind(this); + } + + componentWillMount() { + if (!this.props.lists || this.props.lists.length === 0) { + this.props.fetchLists(); + } } onSubmit() { @@ -45,17 +51,24 @@ class ColumnEditPanelHOC extends Component { }; this.setState({isUpdating: true}); this.props.patchList(listBody) - .then(_ => this.setState({isUpdating: false, open: false})); + .then(_ => this.setState({isUpdating: false}, this.props.onRequestClose)); } - updateList(list, containerType) { + onUpdateList(list, containerType) { this.setState({[containerType]: list, dirty: true}); } + onListPresetSelect(list) { + const fieldsmap = generateTableFieldsmap(list); + const hiddenList = fieldsmap.filter(field => field.hidden && !field.tableOnly); + const showList = fieldsmap.filter(field => !field.hidden && !field.tableOnly); + this.setState({showList, hiddenList, dirty: true}); + } + render() { const state = this.state; const actions = [ - , + , , ]; @@ -66,9 +79,9 @@ class ColumnEditPanelHOC extends Component {
@@ -77,22 +90,25 @@ class ColumnEditPanelHOC extends Component { You can also create custom columns that you can use as template variable in emails.
-
+
There is a number of auto-generated columns that are activated when certain columns are not hidden. For example, activating Instagram Likes and Instagram Comments also activates Likes-to-Comments ratio.
- {/*
Apply Presets - Use properties from a previously created list + Use properties from a previously created list + +
+ diff --git a/js/components/ListTable/EmptyListStatement.jsx b/js/components/ListTable/EmptyListStatement.jsx index 7ff5e891..791c62fd 100644 --- a/js/components/ListTable/EmptyListStatement.jsx +++ b/js/components/ListTable/EmptyListStatement.jsx @@ -3,10 +3,11 @@ import React from 'react'; const EmptyListStatement = ({className, style}) => (
-

You haven't added any contacts. You will see a master sheet of them here after you added some.

+

You haven't added any contact. You will see a master sheet of them here after you added some.

  • "Add Contact" icon on top to add ONE contact
  • Go back to Home and "Upload from Existing" Excel sheet
  • +
  • Want to use same columns as a previous list? Use "Apply Presets" by clicking on icon
); From 2459e64df64a2967f46d43739d89344896e4338e Mon Sep 17 00:00:00 2001 From: Julie Pan Date: Mon, 10 Jul 2017 17:40:18 -0400 Subject: [PATCH 7/8] label change --- .../{ColumnEditPanelHOC => ColumnEditPanel}/Card.jsx | 0 .../ColumnEditPanel.jsx} | 4 ++-- .../{ColumnEditPanelHOC => ColumnEditPanel}/Container.jsx | 0 .../react_sortable_hoc.css | 0 js/components/ListTable/EmptyListStatement.jsx | 2 +- js/components/ListTable/ListTable.jsx | 4 ++-- 6 files changed, 5 insertions(+), 5 deletions(-) rename js/components/ListTable/{ColumnEditPanelHOC => ColumnEditPanel}/Card.jsx (100%) rename js/components/ListTable/{ColumnEditPanelHOC/ColumnEditPanelHOC.jsx => ColumnEditPanel/ColumnEditPanel.jsx} (98%) rename js/components/ListTable/{ColumnEditPanelHOC => ColumnEditPanel}/Container.jsx (100%) rename js/components/ListTable/{ColumnEditPanelHOC => ColumnEditPanel}/react_sortable_hoc.css (100%) diff --git a/js/components/ListTable/ColumnEditPanelHOC/Card.jsx b/js/components/ListTable/ColumnEditPanel/Card.jsx similarity index 100% rename from js/components/ListTable/ColumnEditPanelHOC/Card.jsx rename to js/components/ListTable/ColumnEditPanel/Card.jsx diff --git a/js/components/ListTable/ColumnEditPanelHOC/ColumnEditPanelHOC.jsx b/js/components/ListTable/ColumnEditPanel/ColumnEditPanel.jsx similarity index 98% rename from js/components/ListTable/ColumnEditPanelHOC/ColumnEditPanelHOC.jsx rename to js/components/ListTable/ColumnEditPanel/ColumnEditPanel.jsx index 7b8ed615..46f87a4d 100644 --- a/js/components/ListTable/ColumnEditPanelHOC/ColumnEditPanelHOC.jsx +++ b/js/components/ListTable/ColumnEditPanel/ColumnEditPanel.jsx @@ -20,7 +20,7 @@ alertify.promisifyPrompt = (title, description, defaultValue) => new Promise((re alertify.prompt(title, description, defaultValue, (e, value) => resolve(value), reject); }); -class ColumnEditPanelHOC extends Component { +class ColumnEditPanel extends Component { constructor(props) { super(props); const hiddenList = this.props.fieldsmap.filter(field => field.hidden && !field.tableOnly); @@ -164,4 +164,4 @@ const mapDispatchToProps = (dispatch, props) => { }; }; -export default connect(mapStateToProps, mapDispatchToProps)(DragDropContext(HTML5Backend)(ColumnEditPanelHOC)); +export default connect(mapStateToProps, mapDispatchToProps)(DragDropContext(HTML5Backend)(ColumnEditPanel)); diff --git a/js/components/ListTable/ColumnEditPanelHOC/Container.jsx b/js/components/ListTable/ColumnEditPanel/Container.jsx similarity index 100% rename from js/components/ListTable/ColumnEditPanelHOC/Container.jsx rename to js/components/ListTable/ColumnEditPanel/Container.jsx diff --git a/js/components/ListTable/ColumnEditPanelHOC/react_sortable_hoc.css b/js/components/ListTable/ColumnEditPanel/react_sortable_hoc.css similarity index 100% rename from js/components/ListTable/ColumnEditPanelHOC/react_sortable_hoc.css rename to js/components/ListTable/ColumnEditPanel/react_sortable_hoc.css diff --git a/js/components/ListTable/EmptyListStatement.jsx b/js/components/ListTable/EmptyListStatement.jsx index 791c62fd..5312f406 100644 --- a/js/components/ListTable/EmptyListStatement.jsx +++ b/js/components/ListTable/EmptyListStatement.jsx @@ -7,7 +7,7 @@ const EmptyListStatement = ({className, style}) => (
  • "Add Contact" icon on top to add ONE contact
  • Go back to Home and "Upload from Existing" Excel sheet
  • -
  • Want to use same columns as a previous list? Use "Apply Presets" by clicking on icon
  • +
  • Want to use same columns as an another list? Use "Apply Presets" by clicking on icon
); diff --git a/js/components/ListTable/ListTable.jsx b/js/components/ListTable/ListTable.jsx index 4a6e74ed..003513b8 100644 --- a/js/components/ListTable/ListTable.jsx +++ b/js/components/ListTable/ListTable.jsx @@ -36,7 +36,7 @@ import Drawer from 'material-ui/Drawer'; import {ControlledInput} from '../ToggleableEditInput'; import Waiting from '../Waiting'; import CopyToHOC from './CopyToHOC'; -import ColumnEditPanelHOC from 'components/ListTable/ColumnEditPanelHOC/ColumnEditPanelHOC.jsx'; +import ColumnEditPanel from 'components/ListTable/ColumnEditPanel/ColumnEditPanel.jsx'; import AddContactHOC from './AddContactHOC.jsx'; import AddTagDialogHOC from './AddTagDialogHOC.jsx'; import EditMultipleContactsHOC from './EditMultipleContactsHOC.jsx'; @@ -772,7 +772,7 @@ class ListTable extends Component { iconClassName='fa fa-table' onClick={_ => this.setState({showColumnEditPanel: true})} /> - this.setState({showColumnEditPanel: false})} open={state.showColumnEditPanel} listId={props.listId} /> + this.setState({showColumnEditPanel: false})} open={state.showColumnEditPanel} listId={props.listId} /> {({onRequestOpen}) => ( Date: Mon, 10 Jul 2017 18:16:33 -0400 Subject: [PATCH 8/8] add presets to dropdown from existing lists. about #880 --- css/components/_home.css | 4 ++ js/components/HeaderNaming/HeaderNaming.jsx | 46 ++++++++++++++++--- .../ColumnEditPanel/ColumnEditPanel.jsx | 3 +- .../ListTable/CopyToHOC/CopyToHOC.jsx | 1 + 4 files changed, 46 insertions(+), 8 deletions(-) diff --git a/css/components/_home.css b/css/components/_home.css index 86b009e6..0256ebf4 100644 --- a/css/components/_home.css +++ b/css/components/_home.css @@ -184,3 +184,7 @@ input { .ReactTags__selected .ReactTags__remove { margin-left: 3px; } + +.Select.is-open { + z-index: 100; +} diff --git a/js/components/HeaderNaming/HeaderNaming.jsx b/js/components/HeaderNaming/HeaderNaming.jsx index 281b6977..42a114aa 100644 --- a/js/components/HeaderNaming/HeaderNaming.jsx +++ b/js/components/HeaderNaming/HeaderNaming.jsx @@ -9,8 +9,11 @@ import MenuItem from 'material-ui/MenuItem'; import RaisedButton from 'material-ui/RaisedButton'; import FontIcon from 'material-ui/FontIcon'; import FlatButton from 'material-ui/FlatButton'; +import {generateTableFieldsmap, reformatFieldsmap} from 'components/ListTable/helpers'; +import Select from 'react-select'; +import 'react-select/dist/react-select.css'; -import {grey500, lightBlue50, lightBlue300, red800} from 'material-ui/styles/colors'; +import {grey500, grey600, lightBlue50, lightBlue300, red800} from 'material-ui/styles/colors'; import alertify from 'alertifyjs'; import 'node_modules/alertifyjs/build/css/alertify.min.css'; @@ -36,12 +39,15 @@ class HeaderNaming extends Component { this.state = { order: [], options: defaultSelectableOptions, + seleted: undefined, + isLoading: false }; this.rowRenderer = this._rowRenderer.bind(this); this.headerRenderer = this._headerRenderer.bind(this); this.onMenuChange = this._onMenuChange.bind(this); this.onSubmit = this._onSubmit.bind(this); this.onAddCustom = this._onAddCustom.bind(this); + this.onListPresetSelect = this.onListPresetSelect.bind(this); } componentWillMount() { @@ -159,7 +165,10 @@ class HeaderNaming extends Component { const order = this.state.order.map(name => name || 'ignore_column'); this.props.onAddHeaders(order) .then(_ => { - if (!this.props.didInvalidate) setTimeout(_ => this.props.router.push(`/tables/${this.props.listId}?justCreated=true`), 5000); + if (!this.props.didInvalidate) { + this.setState({isLoading: true}); + setTimeout(_ => this.props.router.push(`/tables/${this.props.listId}?justCreated=true`), 2000); + } }); } @@ -180,12 +189,25 @@ class HeaderNaming extends Component { _ => {}); } + onListPresetSelect(list) { + if (!list) { + this.setState({selected: undefined}); + return; + } + const fieldsmap = generateTableFieldsmap(list) + .filter(field => field.customfield && !field.readonly && !this.state.options.some(option => option.value === field.value)) + .map(field => ({value: field.value, label: field.name, selected: false})); + const options = [...this.state.options, ...fieldsmap]; + this.setState({options, selected: list}, _ => this._headernames.recomputeGridSize()); + } + render() { const props = this.props; const state = this.state; return (
- {props.isReceiving && LOADING ...} + {props.isReceiving && + LOADING ...} {props.headers &&
@@ -195,6 +217,10 @@ class HeaderNaming extends Component { Tabulae will start to aggregate feeds from each contact's social fields once its connected. Upload Guide
+
+ Add Existing List Properties to Dropdown +