From 09b91a0a28b987ce01bc5dc55c2b45cd3bbf5aa4 Mon Sep 17 00:00:00 2001 From: Drew Regitsky Date: Wed, 7 Oct 2015 13:55:54 -0700 Subject: [PATCH] feature(feedback): move feedback to a package, indicator for new msgs Summary: Move all Intercom feedback code to a package. Change the appearance of the lower right question mark icon when a new intercom message is received (red, with repeating CSS bounce animation). New messages are detected by keeping the intercom window open (after the first time it's opened by the user), and listening for DOM mutations of particular classes. Test Plan: manual Reviewers: bengotow Reviewed By: bengotow Subscribers: evan Differential Revision: https://phab.nylas.com/D2125 --- internal_packages/feedback/feedback.html | 124 ++++++++++++++++++ .../feedback/lib/feedback-button.cjsx | 91 +++++++++++++ internal_packages/feedback/lib/main.cjsx | 18 +++ internal_packages/feedback/package.json | 13 ++ .../feedback/stylesheets/main.less | 52 ++++++++ src/flux/stores/analytics-store.coffee | 43 ------ src/sheet-container.cjsx | 6 +- static/feedback.html | 69 ---------- 8 files changed, 299 insertions(+), 117 deletions(-) create mode 100644 internal_packages/feedback/feedback.html create mode 100644 internal_packages/feedback/lib/feedback-button.cjsx create mode 100644 internal_packages/feedback/lib/main.cjsx create mode 100644 internal_packages/feedback/package.json create mode 100644 internal_packages/feedback/stylesheets/main.less delete mode 100644 static/feedback.html diff --git a/internal_packages/feedback/feedback.html b/internal_packages/feedback/feedback.html new file mode 100644 index 000000000..a8dd5f27e --- /dev/null +++ b/internal_packages/feedback/feedback.html @@ -0,0 +1,124 @@ + + + + Feedback + + + + + + diff --git a/internal_packages/feedback/lib/feedback-button.cjsx b/internal_packages/feedback/lib/feedback-button.cjsx new file mode 100644 index 000000000..93c78d86a --- /dev/null +++ b/internal_packages/feedback/lib/feedback-button.cjsx @@ -0,0 +1,91 @@ +{Utils, + React, + FocusedContactsStore, + AccountStore, + Actions} = require 'nylas-exports' +{RetinaImg} = require 'nylas-component-kit' + +class FeedbackButton extends React.Component + @displayName: 'FeedbackButton' + + constructor: (@props) -> + @state = {newMessages: false} + + componentDidMount: => + @unsubscribe = Actions.sendFeedback.listen(@_onSendFeedback) + + componentWillUnmount: => + @unsubscribe() + + render: => +
+
?
+
+ + _getClassName: => + return "btn-feedback" + if @state.newMessages then " newmsg" else "" + + _onSendFeedback: => + return if atom.inSpecMode() + + BrowserWindow = require('remote').require('browser-window') + Screen = require('remote').require('screen') + path = require 'path' + qs = require 'querystring' + + ipc_path = require.resolve("electron-safe-ipc/host") + ipc = require('remote').require(ipc_path) + + if window.feedbackWindow? + window.feedbackWindow.show() + else + + account = AccountStore.current() + params = qs.stringify({ + name: account.name + email: account.emailAddress + accountId: account.id + accountProvider: account.provider + platform: process.platform + provider: account.displayProvider() + organizational_unit: account.organizationUnit + version: atom.getVersion() + }) + + parentBounds = atom.getCurrentWindow().getBounds() + parentScreen = Screen.getDisplayMatching(parentBounds) + + width = 376 + height = Math.min(550, parentBounds.height) + x = Math.min(parentScreen.workAreaSize.width - width, Math.max(0, parentBounds.x + parentBounds.width - 36 - width / 2)) + y = Math.max(0, (parentBounds.y + parentBounds.height) - height - 60) + + window.feedbackWindow = w = new BrowserWindow + 'node-integration': false, + 'web-preferences': {'web-security':false}, + 'x': x + 'y': y + 'width': width, + 'height': height, + 'title': 'Feedback' + + # Disable window close, hide instead + w.on 'close', (event) -> + # inside the window we prevent close - here we route close to hide + event.preventDefault() # this does nothing, contrary to the docs + w.hide() + w.on 'closed', (event) -> + window.feedbackWindow = null # if the window does get closed, clear our ref to it + + ipc.on "fromRenderer", (event,data) => + if event == "newFeedbackMessages" + @setState(newMessages:data) + + url = path.join __dirname, '..', 'feedback.html' + w.loadUrl("file://#{url}?#{params}") + w.show() + + + + +module.exports = FeedbackButton diff --git a/internal_packages/feedback/lib/main.cjsx b/internal_packages/feedback/lib/main.cjsx new file mode 100644 index 000000000..6a88163a0 --- /dev/null +++ b/internal_packages/feedback/lib/main.cjsx @@ -0,0 +1,18 @@ +{WorkspaceStore, ComponentRegistry} = require 'nylas-exports' + +FeedbackButton = require './feedback-button' + + +path = require.resolve("electron-safe-ipc/host") +ipc = require('remote').require(path) + + +module.exports = + activate: (@state) -> + ComponentRegistry.register FeedbackButton, + location: WorkspaceStore.Sheet.Global.Footer + + serialize: -> + + deactivate: -> + ComponentRegistry.unregister(FeedbackButton) diff --git a/internal_packages/feedback/package.json b/internal_packages/feedback/package.json new file mode 100644 index 000000000..e4b98ea59 --- /dev/null +++ b/internal_packages/feedback/package.json @@ -0,0 +1,13 @@ +{ + "name": "feedback", + "main": "./lib/main", + "version": "0.1.0", + "engines": { + "atom": "*" + }, + "description": "Intercom feeedback", + "dependencies": { + "electron-safe-ipc": "^0.5" + }, + "private":true +} \ No newline at end of file diff --git a/internal_packages/feedback/stylesheets/main.less b/internal_packages/feedback/stylesheets/main.less new file mode 100644 index 000000000..5c85a429d --- /dev/null +++ b/internal_packages/feedback/stylesheets/main.less @@ -0,0 +1,52 @@ +@import "ui-variables"; +@import "ui-mixins"; + +.btn-feedback { + position: fixed; + bottom: 10px; + right: 10px; + background: linear-gradient(to bottom, @blue 0%,darken(@blue, 10%) 100%); + width:50px; + height:50px; + border-radius:25px; + display: inline-block; + font-size: 30px; + text-align: center; + line-height:50px; + color:rgba(255,255,255,0.9); + border: 1px solid darken(@blue, 20%); + box-shadow: 0 2px 4px rgba(0,0,0,0.2); + cursor: default; +} +.btn-feedback:hover { + color:rgba(255,255,255,1); + background: linear-gradient(to bottom, lighten(@blue,5%) 0%, darken(@blue, 5%) 100%); +} +.btn-feedback:active { + background: linear-gradient(to bottom, darken(@blue,20%) 0%, darken(@blue, 10%) 100%); +} + +@keyframes bounce { + 46% {right: 10px; animation-timing-function: ease-in;} + 50% {right: 35px; animation-timing-function: ease-in;} + 55% {right: 10px; width: 50px; animation-timing-function: ease-out;} + 58% {right: 3px; width: 48px; animation-timing-function: ease-in;} + 61% {right: 10px; width: 50px; animation-timing-function: ease-out;} + 63% {right: 14px; animation-timing-function: ease-in;} + 65% {right: 10px; animation-timing-function: ease-out;} +} + +.btn-feedback.newmsg{ + background: linear-gradient(to bottom, #F55 0%,darken(#F55, 10%) 100%); + border: 1px solid darken(#F55, 20%); +} +.btn-feedback.newmsg:not(:hover):not(:active){ + animation: bounce 3s ease-in-out 0s infinite; +} +.btn-feedback.newmsg:hover { + color:rgba(255,255,255,1); + background: linear-gradient(to bottom, lighten(#F55,5%) 0%, darken(#F55, 5%) 100%); +} +.btn-feedback.newmsg:active { + background: linear-gradient(to bottom, darken(#F55,20%) 0%, darken(#F55, 10%) 100%); +} \ No newline at end of file diff --git a/src/flux/stores/analytics-store.coffee b/src/flux/stores/analytics-store.coffee index fb3864eef..6ea11591a 100644 --- a/src/flux/stores/analytics-store.coffee +++ b/src/flux/stores/analytics-store.coffee @@ -40,7 +40,6 @@ AnalyticsStore = Reflux.createStore init: -> @analytics = Mixpanel.init("9a2137b80c098b3d594e39b776ebe085") - @listenTo Actions.sendFeedback, @_onSendFeedback @listenTo AccountStore, => @identify() @identify() @@ -92,45 +91,3 @@ AnalyticsStore = Reflux.createStore @analytics.people.set_once(account.id, { "First Seen": (new Date()).toISOString() }) - - _onSendFeedback: -> - return if atom.inSpecMode() - - {AccountStore} = require 'nylas-exports' - BrowserWindow = require('remote').require('browser-window') - Screen = require('remote').require('screen') - path = require 'path' - - account = AccountStore.current() - params = qs.stringify({ - name: account.name - email: account.emailAddress - accountId: account.id - accountProvider: account.provider - platform: process.platform - provider: account.displayProvider() - organizational_unit: account.organizationUnit - version: atom.getVersion() - }) - - parentBounds = atom.getCurrentWindow().getBounds() - parentScreen = Screen.getDisplayMatching(parentBounds) - - width = 376 - height = Math.min(550, parentBounds.height) - x = Math.min(parentScreen.workAreaSize.width - width, Math.max(0, parentBounds.x + parentBounds.width - 36 - width / 2)) - y = Math.max(0, (parentBounds.y + parentBounds.height) - height - 60) - - w = new BrowserWindow - 'node-integration': false, - 'web-preferences': {'web-security':false}, - 'x': x - 'y': y - 'width': width, - 'height': height, - 'title': 'Feedback' - - {resourcePath} = atom.getLoadSettings() - url = path.join(resourcePath, 'static', 'feedback.html') - w.loadUrl("file://#{url}?#{params}") - w.show() diff --git a/src/sheet-container.cjsx b/src/sheet-container.cjsx index eefd66681..7abd128e4 100644 --- a/src/sheet-container.cjsx +++ b/src/sheet-container.cjsx @@ -34,10 +34,6 @@ class SheetContainer extends React.Component sheetElements = @_sheetElements() - feedbackElement = null - if atom.isMainWindow() - feedbackElement =
?
- {@_toolbarContainerElement()} @@ -60,7 +56,7 @@ class SheetContainer extends React.Component - {feedbackElement} + diff --git a/static/feedback.html b/static/feedback.html deleted file mode 100644 index 69aa1a74f..000000000 --- a/static/feedback.html +++ /dev/null @@ -1,69 +0,0 @@ - - - - Feedback - - - - -