Dwayne Harris 5 years ago
parent
commit
c68d20e5a4
  1. 81
      etc/communicator/index.ts
  2. 27
      etc/communicator/webpack.config.ts
  3. 181
      package-lock.json
  4. 31
      package.json
  5. 22
      src/actions/composer.ts
  6. 108
      src/components/composer.tsx
  7. 2
      src/components/create-group-step.tsx
  8. 12
      src/reducers/composer.ts
  9. 2
      src/selectors/composer.ts
  10. 17
      src/types/communicator.ts
  11. 2
      src/types/store.ts
  12. 6
      src/utils/index.ts
  13. 1
      tsconfig.json
  14. 5
      webpack.config.ts

81
etc/communicator/index.ts

@ -0,0 +1,81 @@
import { OutgoingMessageData, MessageContent } from 'src/types/communicator'
declare global {
interface Window { NewFlexorCommunicator: (publicKey: string) => Communicator }
}
interface Listener {
resolve: (value: unknown) => void
reject: (reason: any) => void
once: boolean
}
interface ListenerCollection {
[name: string]: Listener
}
interface AppSettings {
[key: string]: any
}
export class Communicator {
private origin = 'http://localhost:8080'
private publicKey: string
private listeners: ListenerCollection
private settings?: AppSettings
constructor(publicKey: string) {
this.publicKey = publicKey
this.listeners = {}
window.addEventListener('message', (event: MessageEvent) => {
if (event.origin !== this.origin) return
try {
const data = JSON.parse(event.data) as OutgoingMessageData
this.emit(data)
} catch (err) {
console.error(err)
return
}
}, false)
}
private emit(data: OutgoingMessageData) {
const listener = this.listeners[data.name]
if (listener) {
if (data.content) listener.resolve(data.content)
if (data.error) listener.reject(data.error)
if (listener.once) delete this.listeners[data.name]
}
}
private async postAndReceive(name: string, content?: MessageContent) {
if (window.parent) {
window.parent.postMessage(JSON.stringify({
name,
content,
publicKey: this.publicKey,
}), this.origin)
return new Promise((resolve, reject) => {
this.listeners[name] = {
resolve,
reject,
once: true,
}
})
}
}
async init() {
return this.postAndReceive('init')
}
async setHeight(height: number) {
return this.postAndReceive('setHeight', { height })
}
}
window.NewFlexorCommunicator = publicKey => new Communicator(publicKey)

27
etc/communicator/webpack.config.ts

@ -0,0 +1,27 @@
import { resolve } from 'path'
import { Configuration } from 'webpack'
const config: Configuration = {
mode: 'production',
entry: {
communicator: `${__dirname}/index.ts`
},
output: {
path: resolve(__dirname, '../../dist'),
filename: '[name].js',
},
resolve: {
extensions: ['.ts'],
},
module: {
rules: [
{
test: /\.ts(x?)$/,
exclude: /node_modules/,
use: 'ts-loader',
},
],
},
}
export default config

181
package-lock.json

@ -83,9 +83,9 @@
}
},
"@fortawesome/react-fontawesome": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.5.tgz",
"integrity": "sha512-WYDKTgyAWOncujWhhzhW7k8sgO5Eo2pZTUL51yNzSQNBUwwr6rNKg/JUSE3iebaU1XShHw74aKc1kJ+jvtRNew==",
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.7.tgz",
"integrity": "sha512-AHWSzOsHBe5vqOkrvs+CKw+8eLl+0XZsVixOWhTPpGpOA8WQUbVU6J9cmtAvTaxUU5OIf+rgxxF8ZKc3BVldxg==",
"requires": {
"prop-types": "^15.5.10"
}
@ -158,9 +158,9 @@
}
},
"@types/express-serve-static-core": {
"version": "4.16.9",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.16.9.tgz",
"integrity": "sha512-GqpaVWR0DM8FnRUJYKlWgyARoBUAVfRIeVDZQKOttLFp5SmhhF9YFIYeTPwMd/AXfxlP7xVO2dj1fGu0Q+krKQ==",
"version": "4.16.10",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.16.10.tgz",
"integrity": "sha512-gM6evDj0OvTILTRKilh9T5dTaGpv1oYiFcJAfgSejuMJgGJUsD9hKEU2lB4aiTNy4WwChxRnjfYFuBQsULzsJw==",
"dev": true,
"requires": {
"@types/node": "*",
@ -237,9 +237,9 @@
}
},
"@types/lodash": {
"version": "4.14.141",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.141.tgz",
"integrity": "sha512-v5NYIi9qEbFEUpCyikmnOYe4YlP8BMUdTcNCAquAKzu+FA7rZ1onj9x80mbnDdOW/K5bFf3Tv5kJplP33+gAbQ==",
"version": "4.14.144",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.144.tgz",
"integrity": "sha512-ogI4g9W5qIQQUhXAclq6zhqgqNUr7UlFaqDHbch7WLSLeeM/7d3CRaw7GLajxvyFvhJqw4Rpcz5bhoaYtIx6Tg==",
"dev": true
},
"@types/mime": {
@ -289,37 +289,28 @@
"dev": true
},
"@types/react": {
"version": "16.9.5",
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.5.tgz",
"integrity": "sha512-jQ12VMiFOWYlp+j66dghOWcmDDwhca0bnlcTxS4Qz/fh5gi6wpaZDthPEu/Gc/YlAuO87vbiUXL8qKstFvuOaA==",
"version": "16.9.9",
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.9.tgz",
"integrity": "sha512-L+AudFJkDukk+ukInYvpoAPyJK5q1GanFOINOJnM0w6tUgITuWvJ4jyoBPFL7z4/L8hGLd+K/6xR5uUjXu0vVg==",
"dev": true,
"requires": {
"@types/prop-types": "*",
"csstype": "^2.2.0"
}
},
"@types/react-avatar-editor": {
"version": "10.3.4",
"resolved": "https://registry.npmjs.org/@types/react-avatar-editor/-/react-avatar-editor-10.3.4.tgz",
"integrity": "sha512-yulle6pZw+7jCxq156WbIqT0qwuLtzxt53fCIm1op0IFUotChAYYSZmKQR2+4jjhToHrX53nR83Slvr8H6ar5Q==",
"dev": true,
"requires": {
"@types/react": "*"
}
},
"@types/react-dom": {
"version": "16.9.1",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.1.tgz",
"integrity": "sha512-1S/akvkKr63qIUWVu5IKYou2P9fHLb/P2VAwyxVV85JGaGZTcUniMiTuIqM3lXFB25ej6h+CYEQ27ERVwi6eGA==",
"version": "16.9.2",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.2.tgz",
"integrity": "sha512-hgPbBoI1aTSTvZwo8HYw35UaTldW6n2ETLvHAcfcg1FaOuBV3olmyCe5eMpx2WybWMBPv0MdU2t5GOcQhP+3zA==",
"dev": true,
"requires": {
"@types/react": "*"
}
},
"@types/react-redux": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.4.tgz",
"integrity": "sha512-SUV/7d+4L7C1Db/D4pqASgN1V1U2HnDEhEol9lYpPSguS76xFboZzf5ha2hTz6v31cUewyC7WksMh1q8JxhebQ==",
"version": "7.1.5",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.5.tgz",
"integrity": "sha512-ZoNGQMDxh5ENY7PzU7MVonxDzS1l/EWiy8nUhDqxFqUZn4ovboCyvk4Djf68x6COb7vhGTKjyjxHxtFdAA5sUA==",
"dev": true,
"requires": {
"@types/hoist-non-react-statics": "^3.3.0",
@ -435,9 +426,9 @@
}
},
"@types/webpack": {
"version": "4.39.2",
"resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.39.2.tgz",
"integrity": "sha512-3c7+vcmyyIi3RBoOdXs8k3E9rQVIy6yOBqK0DFk6lnJ76JUfbDBWbEf1JflzyPQf56W4ToE+2YPnbxbucniW5w==",
"version": "4.39.5",
"resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.39.5.tgz",
"integrity": "sha512-9twG6D97ao13MBLvigwfBJe6rxtb04UY3TcYHBYkW5sXZjUrNhqIRxLYg74VzK/YAE8xlVhOyd+3Whr7E5RrBA==",
"dev": true,
"requires": {
"@types/anymatch": "*",
@ -458,9 +449,9 @@
}
},
"@types/webpack-dev-server": {
"version": "3.1.7",
"resolved": "https://registry.npmjs.org/@types/webpack-dev-server/-/webpack-dev-server-3.1.7.tgz",
"integrity": "sha512-VIRkDkBDuOkYRXQ1EG/etisQ3odo6pcjSmA1Si4VYANuNhSBsLxfuPGeGERwCx1nDKxK3aaXnicPzi0gUvxUaw==",
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@types/webpack-dev-server/-/webpack-dev-server-3.4.0.tgz",
"integrity": "sha512-cNxiXfGnMxVgXOjmo/SQdsIX2Muan0A44AvPgVfz9y1PfWogOJGEy+/nFkrF/luvFxykJXT+fZYPpyuIGZtRZg==",
"dev": true,
"requires": {
"@types/connect-history-api-fallback": "*",
@ -1025,10 +1016,13 @@
"dev": true
},
"async": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
"dev": true
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
"dev": true,
"requires": {
"lodash": "^4.17.14"
}
},
"async-each": {
"version": "1.0.3",
@ -1187,9 +1181,9 @@
}
},
"bluebird": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.0.tgz",
"integrity": "sha512-aBQ1FxIa7kSWCcmKHlcHFlT2jt6J/l4FzC7KcPELkOJOsPOb/bccdhmIrKDfXhwFrmc7vDoDrrepFvGqjyXGJg==",
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.1.tgz",
"integrity": "sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg==",
"dev": true
},
"bn.js": {
@ -1376,9 +1370,9 @@
"dev": true
},
"bulma": {
"version": "0.7.5",
"resolved": "https://registry.npmjs.org/bulma/-/bulma-0.7.5.tgz",
"integrity": "sha512-cX98TIn0I6sKba/DhW0FBjtaDpxTelU166pf7ICXpCCuplHWyu6C9LYZmL5PEsnePIeJaiorsTEzzNk3Tsm1hw==",
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/bulma/-/bulma-0.8.0.tgz",
"integrity": "sha512-nhf3rGyiZh/VM7FrSJ/5KeLlfaFkXz0nYcXriynfPH4vVpnxnqyEwaNGdNCVzHyyCA3cHgkQAMpdF/SFbFGZfA==",
"dev": true
},
"bytes": {
@ -1705,9 +1699,9 @@
}
},
"commander": {
"version": "2.20.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.1.tgz",
"integrity": "sha512-cCuLsMhJeWQ/ZpsFTbE765kvVfoeSddc4nU3up4fV+fDBcfUXnbITJ+JzhkdjzOqhURjZgujxaioam4RM9yGUg==",
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
},
"commondir": {
@ -5923,14 +5917,31 @@
}
},
"portfinder": {
"version": "1.0.24",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.24.tgz",
"integrity": "sha512-ekRl7zD2qxYndYflwiryJwMioBI7LI7rVXg3EnLK3sjkouT5eOuhS3gS255XxBksa30VG8UPZYZCdgfGOfkSUg==",
"version": "1.0.25",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz",
"integrity": "sha512-6ElJnHBbxVA1XSLgBp7G1FiCkQdlqGzuF7DswL5tcea+E8UpuvPU7beVAjjRwCioTS9ZluNbu+ZyRvgTsmqEBg==",
"dev": true,
"requires": {
"async": "^1.5.2",
"debug": "^2.2.0",
"mkdirp": "0.5.x"
"async": "^2.6.2",
"debug": "^3.1.1",
"mkdirp": "^0.5.1"
},
"dependencies": {
"debug": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
"dev": true,
"requires": {
"ms": "^2.1.1"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
}
}
},
"posix-character-classes": {
@ -6225,32 +6236,24 @@
"integrity": "sha512-JsecfN+JlckncVXTWFWjn0Vk6uInl8GSf4eEd9tTk5qXHlgqkPdILpnYpgZcISXNYAzvfvsCZviaDk8AxyS5sg=="
},
"react": {
"version": "16.10.2",
"resolved": "https://registry.npmjs.org/react/-/react-16.10.2.tgz",
"integrity": "sha512-MFVIq0DpIhrHFyqLU0S3+4dIcBhhOvBE8bJ/5kHPVOVaGdo0KuiQzpcjCPsf585WvhypqtrMILyoE2th6dT+Lw==",
"version": "16.11.0",
"resolved": "https://registry.npmjs.org/react/-/react-16.11.0.tgz",
"integrity": "sha512-M5Y8yITaLmU0ynd0r1Yvfq98Rmll6q8AxaEe88c8e7LxO8fZ2cNgmFt0aGAS9wzf1Ao32NKXtCl+/tVVtkxq6g==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2"
}
},
"react-avatar-editor": {
"version": "11.0.7",
"resolved": "https://registry.npmjs.org/react-avatar-editor/-/react-avatar-editor-11.0.7.tgz",
"integrity": "sha512-GbNYBd1/L1QyuU9VRvOW0hSkW1R0XSneOWZFgqI5phQf6dX+dF/G3/AjiJ0hv3JWh2irMQ7DL0oYDKzwtTnNBQ==",
"requires": {
"prop-types": "^15.5.8"
}
},
"react-dom": {
"version": "16.10.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.10.2.tgz",
"integrity": "sha512-kWGDcH3ItJK4+6Pl9DZB16BXYAZyrYQItU4OMy0jAkv5aNqc+mAKb4TpFtAteI6TJZu+9ZlNhaeNQSVQDHJzkw==",
"version": "16.11.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.11.0.tgz",
"integrity": "sha512-nrRyIUE1e7j8PaXSPtyRKtz+2y9ubW/ghNgqKFHHAHaeP0fpF5uXR+sq8IMRHC+ZUxw7W9NyCDTBtwWxvkb0iA==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2",
"scheduler": "^0.16.2"
"scheduler": "^0.17.0"
}
},
"react-is": {
@ -7031,9 +7034,9 @@
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
},
"scheduler": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.16.2.tgz",
"integrity": "sha512-BqYVWqwz6s1wZMhjFvLfVR5WXP7ZY32M/wYPo04CcuPM7XZEbV2TBNW7Z0UkguPTl0dWMA59VbNXxK6q+pHItg==",
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.17.0.tgz",
"integrity": "sha512-7rro8Io3tnCPuY4la/NuI5F2yfESpnfZyT6TtkXnSWVkcu0BCDJ+8gk5ozUaFaxpIyNuWAPXrH0yFcSi28fnDA==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
@ -7815,9 +7818,9 @@
}
},
"terser": {
"version": "4.3.8",
"resolved": "https://registry.npmjs.org/terser/-/terser-4.3.8.tgz",
"integrity": "sha512-otmIRlRVmLChAWsnSFNO0Bfk6YySuBp6G9qrHiJwlLDd4mxe2ta4sjI7TzIR+W1nBMjilzrMcPOz9pSusgx3hQ==",
"version": "4.3.9",
"resolved": "https://registry.npmjs.org/terser/-/terser-4.3.9.tgz",
"integrity": "sha512-NFGMpHjlzmyOtPL+fDw3G7+6Ueh/sz4mkaUYa4lJCxOPTNzd0Uj0aZJOmsDYoSQyfuVoWDMSWTPU3huyOm2zdA==",
"dev": true,
"requires": {
"commander": "^2.20.0",
@ -7853,9 +7856,9 @@
}
},
"thunky": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/thunky/-/thunky-1.0.3.tgz",
"integrity": "sha512-YwT8pjmNcAXBZqrubu22P4FYsh2D4dxRmnWBOL8Jk8bUcRUtc5326kx32tuTmFDAZtLOGEVNl8POAR8j896Iow==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
"integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==",
"dev": true
},
"timers-browserify": {
@ -8049,9 +8052,9 @@
"dev": true
},
"typescript": {
"version": "3.6.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.3.tgz",
"integrity": "sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw==",
"version": "3.6.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.4.tgz",
"integrity": "sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg==",
"dev": true
},
"uglify-js": {
@ -8335,9 +8338,9 @@
}
},
"webpack": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.41.0.tgz",
"integrity": "sha512-yNV98U4r7wX1VJAj5kyMsu36T8RPPQntcb5fJLOsMz/pt/WrKC0Vp1bAlqPLkA1LegSwQwf6P+kAbyhRKVQ72g==",
"version": "4.41.2",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.41.2.tgz",
"integrity": "sha512-Zhw69edTGfbz9/8JJoyRQ/pq8FYUoY0diOXqW0T6yhgdhCv6wr0hra5DwwWexNRns2Z2+gsnrNcbe9hbGBgk/A==",
"dev": true,
"requires": {
"@webassemblyjs/ast": "1.8.5",
@ -8471,9 +8474,9 @@
}
},
"webpack-bundle-analyzer": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.5.2.tgz",
"integrity": "sha512-g9spCNe25QYUVqHRDkwG414GTok2m7pTTP0wr6l0J50Z3YLS04+BGodTqqoVBL7QfU/U/9p/oiI5XFOyfZ7S/A==",
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.6.0.tgz",
"integrity": "sha512-orUfvVYEfBMDXgEKAKVvab5iQ2wXneIEorGNsyuOyVYpjYrI7CUOhhXNDd3huMwQ3vNNWWlGP+hzflMFYNzi2g==",
"dev": true,
"requires": {
"acorn": "^6.0.7",
@ -8543,9 +8546,9 @@
}
},
"webpack-dev-server": {
"version": "3.8.2",
"resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.8.2.tgz",
"integrity": "sha512-0xxogS7n5jHDQWy0WST0q6Ykp7UGj4YvWh+HVN71JoE7BwPxMZrwgraBvmdEMbDVMBzF0u+mEzn8TQzBm5NYJQ==",
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.9.0.tgz",
"integrity": "sha512-E6uQ4kRrTX9URN9s/lIbqTAztwEPdvzVrcmHE8EQ9YnuT9J8Es5Wrd8n9BKg1a0oZ5EgEke/EQFgUsp18dSTBw==",
"dev": true,
"requires": {
"ansi-html": "0.0.7",
@ -8566,7 +8569,7 @@
"loglevel": "^1.6.4",
"opn": "^5.5.0",
"p-retry": "^3.0.1",
"portfinder": "^1.0.24",
"portfinder": "^1.0.25",
"schema-utils": "^1.0.0",
"selfsigned": "^1.10.7",
"semver": "^6.3.0",

31
package.json

@ -8,6 +8,7 @@
"build": "npm run build:dev",
"build:dev": "webpack --config webpack.config.ts",
"build:prod": "webpack --mode production --config webpack.config.ts",
"build:communicator": "webpack --config etc/communicator/webpack.config.ts",
"deploy:batch:dev": "az storage blob upload-batch -s ./dist/ -d $web --account-name flexordev",
"deploy:batch:prod": "az storage blob upload-batch -s ./dist/ -d $web --account-name flexor",
"deploy:config:dev": "az storage blob upload -f ./config/dev.json -c flexordev -n config.json",
@ -18,20 +19,19 @@
"devDependencies": {
"@types/classnames": "^2.2.9",
"@types/html-webpack-plugin": "^3.2.1",
"@types/lodash": "^4.14.141",
"@types/lodash": "^4.14.144",
"@types/mini-css-extract-plugin": "^0.8.0",
"@types/react": "^16.9.5",
"@types/react-avatar-editor": "^10.3.4",
"@types/react-dom": "^16.9.1",
"@types/react-redux": "^7.1.4",
"@types/react": "^16.9.9",
"@types/react-dom": "^16.9.2",
"@types/react-redux": "^7.1.5",
"@types/react-router-dom": "^5.1.0",
"@types/redux-logger": "^3.0.7",
"@types/uuid": "^3.4.5",
"@types/webpack": "^4.39.2",
"@types/webpack": "^4.39.5",
"@types/webpack-bundle-analyzer": "^2.13.3",
"@types/webpack-dev-server": "^3.1.7",
"@types/webpack-dev-server": "^3.4.0",
"@types/zxcvbn": "^4.4.0",
"bulma": "^0.7.5",
"bulma": "^0.8.0",
"css-loader": "^3.2.0",
"html-webpack-plugin": "^3.2.0",
"mini-css-extract-plugin": "^0.8.0",
@ -41,26 +41,25 @@
"style-loader": "^1.0.0",
"ts-loader": "^6.2.0",
"ts-node": "^8.4.1",
"typescript": "^3.6.3",
"webpack": "^4.41.0",
"webpack-bundle-analyzer": "^3.5.2",
"typescript": "^3.6.4",
"webpack": "^4.41.2",
"webpack-bundle-analyzer": "^3.6.0",
"webpack-cli": "^3.3.9",
"webpack-dev-server": "^3.8.2"
"webpack-dev-server": "^3.9.0"
},
"dependencies": {
"@azure/storage-blob": "^10.5.0",
"@fortawesome/fontawesome-common-types": "^0.2.25",
"@fortawesome/fontawesome-svg-core": "^1.2.25",
"@fortawesome/free-solid-svg-icons": "^5.11.2",
"@fortawesome/react-fontawesome": "^0.1.5",
"@fortawesome/react-fontawesome": "^0.1.7",
"classnames": "^2.2.6",
"history": "^4.10.1",
"lodash": "^4.17.15",
"moment": "^2.24.0",
"re-reselect": "^3.4.0",
"react": "^16.10.2",
"react-avatar-editor": "^11.0.7",
"react-dom": "^16.10.2",
"react": "^16.11.0",
"react-dom": "^16.11.0",
"react-redux": "^7.1.1",
"react-router-dom": "^5.1.2",
"redux": "^4.0.4",

22
src/actions/composer.ts

@ -16,7 +16,17 @@ export interface SetSelectedInstallationAction extends Action {
payload?: string
}
export type ComposerActions = SetInstallationsAction | SetSelectedInstallationAction
export interface SetHeightAction extends Action {
type: 'COMPOSER_SET_HEIGHT',
payload: number
}
export interface SetErrorAction extends Action {
type: 'COMPOSER_SET_ERROR',
payload?: string
}
export type ComposerActions = SetInstallationsAction | SetSelectedInstallationAction | SetHeightAction | SetErrorAction
export const setInstallations = (installations: string[]): SetInstallationsAction => ({
type: 'COMPOSER_SET_INSTALLATIONS',
@ -28,6 +38,16 @@ export const setSelectedInstallation = (installation?: string): SetSelectedInsta
payload: installation,
})
export const setHeight = (height: number): SetHeightAction => ({
type: 'COMPOSER_SET_HEIGHT',
payload: height,
})
export const setError = (error?: string): SetErrorAction => ({
type: 'COMPOSER_SET_ERROR',
payload: error,
})
interface FetchInstallationsResponse {
installations: Installation[]
}

108
src/components/composer.tsx

@ -1,23 +1,101 @@
import React, { FC, useEffect } from 'react'
import React, { FC, useEffect, useRef } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import classNames from 'classnames'
import { useConfig } from 'src/hooks'
import { fetchInstallations, setSelectedInstallation } from 'src/actions/composer'
import { getInstallations, getSelectedInstallation } from 'src/selectors/composer'
import { AppState, Installation } from 'src/types'
import { getOrigin } from 'src/utils'
import { useConfig, useDeepCompareEffect } from 'src/hooks'
import { fetchInstallations, setSelectedInstallation, setHeight as setComposerHeight, setError as setComposerError } from 'src/actions/composer'
import { getInstallations, getSelectedInstallation, getError, getHeight as getComposerHeight } from 'src/selectors/composer'
import { AppState, Installation, ClassDictionary } from 'src/types'
import { IncomingMessageData, OutgoingMessageData } from 'src/types/communicator'
const Composer: FC = () => {
const installations = useSelector<AppState, Installation[]>(getInstallations)
const selected = useSelector<AppState, Installation | undefined>(getSelectedInstallation)
const installation = useSelector<AppState, Installation | undefined>(getSelectedInstallation)
const height = useSelector<AppState, number>(getComposerHeight)
const error = useSelector<AppState, string | undefined>(getError)
const config = useConfig()
const dispatch = useDispatch()
const ref = useRef<HTMLIFrameElement>(null)
const composerUrl = installation ? installation.app.composerUrl : undefined
const showComposer = !!composerUrl && !error
const composerClasses: ClassDictionary = {
composer: true,
'composer-empty': !showComposer,
}
useEffect(() => {
dispatch(fetchInstallations())
}, [])
useDeepCompareEffect(() => {
if (!composerUrl) return
if (!installation) return
if (error) return
const listener = async (event: MessageEvent) => {
const origin = getOrigin(composerUrl)
if (event.origin !== origin) return
const postMessage = (message: OutgoingMessageData) => {
if (ref.current && ref.current.contentWindow) {
ref.current.contentWindow.postMessage(JSON.stringify(message), origin)
}
}
let data: IncomingMessageData | undefined
try {
data = JSON.parse(event.data)
} catch (err) {
dispatch(setComposerError('Invalid payload'))
return
}
if (!data) return
if (data.publicKey !== installation.app.publicKey) {
const message = 'Invalid publicKey'
dispatch(setComposerError(message))
postMessage({
name: data.name,
error: message,
})
}
switch (data.name) {
case 'init':
postMessage({
name: data.name,
settings: installation.settings,
})
break
case 'setHeight':
const { height = 100 } = data.content
dispatch(setComposerHeight(Math.max(Math.min(height, 400), 100)))
postMessage({
name: data.name,
})
break
}
}
window.addEventListener('message', listener, false)
return () => {
window.removeEventListener('message', listener, false)
}
}, [installation, error])
const handleClick = (id: string) => {
if (selected && selected.id === id) {
if (installation && installation.id === id) {
dispatch(setSelectedInstallation())
return
}
@ -27,15 +105,19 @@ const Composer: FC = () => {
return (
<div className="container composer-container">
<div className="composer composer-empty">
<p>{selected ? selected.app.name : 'Choose an app.'}</p>
<div className={classNames(composerClasses)}>
{showComposer &&
<iframe ref={ref} src={composerUrl} scrolling="no" style={{ height, width: '100%', overflow: 'hidden' }} />
}
{error && <span className="has-text-danger">Composer Error: {error}</span>}
{(!showComposer && !error) && <span>Choose an App.</span>}
</div>
<div className="installations is-flex">
{installations.map(installation => (
<div key={installation.id} className={selected && selected.id === installation.id ? 'selected' : ''} onClick={() => handleClick(installation.id)}>
<img src={`${config.blobUrl}${installation.app.iconImageUrl}`} alt={installation.app.name} style={{ width: 32 }} />
<p className="is-size-7 has-text-weight-bold">{installation.app.name}</p>
{installations.map(i => (
<div key={i.id} className={installation && installation.id === i.id ? 'selected' : ''} onClick={() => handleClick(i.id)}>
<img src={`${config.blobUrl}${i.app.iconImageUrl}`} alt={i.app.name} style={{ width: 32 }} />
<p className="is-size-7 has-text-weight-bold">{i.app.name}</p>
</div>
))}
</div>

2
src/components/create-group-step.tsx

@ -6,7 +6,7 @@ import { faBuilding, faArrowLeft } from '@fortawesome/free-solid-svg-icons'
import { setFieldNotification } from 'src/actions/forms'
import { showNotification } from 'src/actions/notifications'
import { setStep } from 'src/actions/registration'
import { getForm, getFieldValue } from 'src/selectors/forms'
import { getForm } from 'src/selectors/forms'
import { MAX_ID_LENGTH } from 'src/constants'
import { AppState, AppThunkDispatch, NotificationType, Form } from 'src/types'

12
src/reducers/composer.ts

@ -6,6 +6,8 @@ import { ComposerState } from '../types'
const initialState: ComposerState = {
installations: [],
selected: undefined,
error: undefined,
height: 100,
}
const reducer: Reducer<ComposerState, ComposerActions> = (state = initialState, action) => {
@ -20,6 +22,16 @@ const reducer: Reducer<ComposerState, ComposerActions> = (state = initialState,
...state,
selected: action.payload,
}
case 'COMPOSER_SET_HEIGHT':
return {
...state,
height: action.payload,
}
case 'COMPOSER_SET_ERROR':
return {
...state,
error: action.payload,
}
default:
return state
}

2
src/selectors/composer.ts

@ -4,6 +4,8 @@ import { AppState, EntityType, App, Installation } from 'src/types'
export const getApps = (state: AppState) => denormalize(state.apps.items, EntityType.App, state.entities) as App[]
export const getCreatedApps = (state: AppState) => denormalize(state.apps.created.items, EntityType.App, state.entities) as App[]
export const getInstallations = (state: AppState) => denormalize(state.composer.installations, EntityType.Installation, state.entities) as Installation[]
export const getError = (state: AppState) => state.composer.error
export const getHeight = (state: AppState) => state.composer.height
export const getSelectedInstallation = (state: AppState) => {
if (!state.composer.selected) return

17
src/types/communicator.ts

@ -0,0 +1,17 @@
export interface MessageContent {
[key: string]: any
height?: number
}
export interface IncomingMessageData {
name: string
content: MessageContent
publicKey: string
}
export interface OutgoingMessageData {
name: string
content?: MessageContent
error?: string
settings?: object
}

2
src/types/store.ts

@ -111,6 +111,8 @@ export type AppsState = EntityList & {
export type ComposerState = {
installations: string[]
selected?: string
error?: string
height: number
}
export type ConfigState = Config

6
src/utils/index.ts

@ -44,3 +44,9 @@ export async function urlForBlobAsync(name: string) {
const config = await getConfig()
return urlForBlob(config, name)
}
export function getOrigin(url: string) {
const parser = document.createElement('a')
parser.href = url
return parser.origin
}

1
tsconfig.json

@ -1,6 +1,5 @@
{
"compilerOptions": {
"outDir": "./dist/",
"baseUrl": ".",
"sourceMap": true,
"strict": true,

5
webpack.config.ts

@ -34,11 +34,6 @@ const config: Configuration = {
extensions: ['.ts', '.tsx', '.js'],
alias: {
src: `${__dirname}/src`,
actions: `${__dirname}/src/actions/`,
components: `${__dirname}/src/components/`,
reducers: `${__dirname}/src/reducers/`,
selectors: `${__dirname}/src/selectors/`,
types: `${__dirname}/src/types/`,
}
},
module: {

Loading…
Cancel
Save