Dwayne Harris 5 years ago
parent
commit
0e6938b7e4
  1. 283
      package-lock.json
  2. 13
      package.json
  3. 163
      src/apps/gif-app/composer/app.tsx
  4. 24
      src/apps/gif-app/composer/gif.tsx
  5. BIN
      src/apps/gif-app/composer/giphy.png
  6. 12
      src/apps/gif-app/composer/index.ejs
  7. 6
      src/apps/gif-app/composer/index.tsx
  8. 63
      src/apps/gif-app/composer/webpack.config.ts
  9. 17
      src/apps/text-app/composer/app.tsx
  10. 2
      src/apps/text-app/composer/index.ejs
  11. 4
      src/apps/text-app/composer/index.tsx
  12. 1
      src/apps/text-app/composer/webpack.config.ts
  13. 16
      src/communicator/index.ts
  14. 145
      src/server/api/index.ts
  15. 14
      src/server/index.ts
  16. 17
      src/styles/default.scss
  17. 58
      src/types/index.ts
  18. 2
      tsconfig.json

283
package-lock.json

@ -45,6 +45,12 @@
"integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==",
"dev": true
},
"@types/caseless": {
"version": "0.12.2",
"resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz",
"integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==",
"dev": true
},
"@types/classnames": {
"version": "2.2.9",
"resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.2.9.tgz",
@ -128,6 +134,31 @@
"integrity": "sha1-a9p9uGU/piZD9e5p6facEaOS46Y=",
"dev": true
},
"@types/request": {
"version": "2.48.3",
"resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.3.tgz",
"integrity": "sha512-3Wo2jNYwqgXcIz/rrq18AdOZUQB8cQ34CXZo+LUwPJNpvRAL86+Kc2wwI8mqpz9Cr1V+enIox5v+WZhy/p3h8w==",
"dev": true,
"requires": {
"@types/caseless": "*",
"@types/node": "*",
"@types/tough-cookie": "*",
"form-data": "^2.5.0"
},
"dependencies": {
"form-data": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz",
"integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==",
"dev": true,
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.6",
"mime-types": "^2.1.12"
}
}
}
},
"@types/source-list-map": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz",
@ -140,6 +171,12 @@
"integrity": "sha512-78AdXtlhpCHT0K3EytMpn4JNxaf5tbqbLcbIRoQIHzpTIyjpxLQKRoxU55ujBXAtg3Nl2h/XWvfDa9dsMOd0pQ==",
"dev": true
},
"@types/tough-cookie": {
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.5.tgz",
"integrity": "sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg==",
"dev": true
},
"@types/uglify-js": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.0.4.tgz",
@ -557,7 +594,6 @@
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
"integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
"dev": true,
"requires": {
"safer-buffer": "~2.1.0"
}
@ -603,8 +639,7 @@
"assert-plus": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
"dev": true
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
},
"assign-symbols": {
"version": "1.0.0",
@ -627,8 +662,7 @@
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
"dev": true
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
},
"atob": {
"version": "2.1.2",
@ -664,14 +698,12 @@
"aws-sign2": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
"integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=",
"dev": true
"integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
},
"aws4": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==",
"dev": true
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
},
"balanced-match": {
"version": "1.0.0",
@ -743,7 +775,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
"integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
"dev": true,
"requires": {
"tweetnacl": "^0.14.3"
}
@ -1014,8 +1045,7 @@
"caseless": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=",
"dev": true
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
},
"chalk": {
"version": "2.4.2",
@ -1195,7 +1225,6 @@
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dev": true,
"requires": {
"delayed-stream": "~1.0.0"
}
@ -1311,8 +1340,7 @@
"core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
"dev": true
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"create-ecdh": {
"version": "4.0.3",
@ -1490,7 +1518,6 @@
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
"integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
"dev": true,
"requires": {
"assert-plus": "^1.0.0"
}
@ -1585,8 +1612,7 @@
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
"dev": true
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
},
"delegates": {
"version": "1.0.0",
@ -1695,6 +1721,11 @@
"domelementtype": "1"
}
},
"dotenv": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz",
"integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw=="
},
"duplexify": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
@ -1743,7 +1774,6 @@
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
"integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
"dev": true,
"requires": {
"jsbn": "~0.1.0",
"safer-buffer": "^2.1.0"
@ -2020,8 +2050,7 @@
"extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"dev": true
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
},
"extend-shallow": {
"version": "3.0.2",
@ -2112,8 +2141,7 @@
"extsprintf": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
"dev": true
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
},
"fast-decode-uri-component": {
"version": "1.0.1",
@ -2210,6 +2238,54 @@
"integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==",
"dev": true
},
"file-loader": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/file-loader/-/file-loader-4.2.0.tgz",
"integrity": "sha512-+xZnaK5R8kBJrHK0/6HRlrKNamvVS5rjyuju+rnyxRGuwUJwpAMsVzUl5dz6rK8brkzjV6JpcFNjp6NqV0g1OQ==",
"dev": true,
"requires": {
"loader-utils": "^1.2.3",
"schema-utils": "^2.0.0"
},
"dependencies": {
"big.js": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
"integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
"dev": true
},
"json5": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
"dev": true,
"requires": {
"minimist": "^1.2.0"
}
},
"loader-utils": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz",
"integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==",
"dev": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^2.0.0",
"json5": "^1.0.1"
}
},
"schema-utils": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.5.0.tgz",
"integrity": "sha512-32ISrwW2scPXHUSusP8qMg5dLUawKkyV+/qIEV9JdXKx+rsM6mi8vZY8khg2M69Qom16rtroWXD3Ybtiws38gQ==",
"dev": true,
"requires": {
"ajv": "^6.10.2",
"ajv-keywords": "^3.4.1"
}
}
}
},
"fill-range": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
@ -2331,14 +2407,12 @@
"forever-agent": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
"integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
"dev": true
"integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
},
"form-data": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
"dev": true,
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.6",
@ -3099,7 +3173,6 @@
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
"integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
"dev": true,
"requires": {
"assert-plus": "^1.0.0"
}
@ -3193,14 +3266,12 @@
"har-schema": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
"integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
"dev": true
"integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
},
"har-validator": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
"integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
"dev": true,
"requires": {
"ajv": "^6.5.5",
"har-schema": "^2.0.0"
@ -3400,7 +3471,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
"integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
"dev": true,
"requires": {
"assert-plus": "^1.0.0",
"jsprim": "^1.2.2",
@ -3704,8 +3774,7 @@
"is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
"dev": true
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
},
"is-utf8": {
"version": "0.2.1",
@ -3746,8 +3815,7 @@
"isstream": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
"dev": true
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
},
"jmespath": {
"version": "0.15.0",
@ -3769,8 +3837,7 @@
"jsbn": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
"dev": true
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
},
"json-parse-better-errors": {
"version": "1.0.2",
@ -3781,8 +3848,7 @@
"json-schema": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
"integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
"dev": true
"integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
},
"json-schema-traverse": {
"version": "0.4.1",
@ -3792,8 +3858,7 @@
"json-stringify-safe": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
"dev": true
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
},
"json5": {
"version": "0.5.1",
@ -3805,7 +3870,6 @@
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
"integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
"dev": true,
"requires": {
"assert-plus": "1.0.0",
"extsprintf": "1.3.0",
@ -4047,6 +4111,12 @@
}
}
},
"memorystream": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz",
"integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=",
"dev": true
},
"meow": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
@ -4113,14 +4183,12 @@
"mime-db": {
"version": "1.40.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
"integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==",
"dev": true
"integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA=="
},
"mime-types": {
"version": "2.1.24",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz",
"integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
"dev": true,
"requires": {
"mime-db": "1.40.0"
}
@ -4540,6 +4608,79 @@
"sort-keys": "^1.0.0"
}
},
"npm-run-all": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz",
"integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"chalk": "^2.4.1",
"cross-spawn": "^6.0.5",
"memorystream": "^0.3.1",
"minimatch": "^3.0.4",
"pidtree": "^0.3.0",
"read-pkg": "^3.0.0",
"shell-quote": "^1.6.1",
"string.prototype.padend": "^3.0.0"
},
"dependencies": {
"load-json-file": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
"integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=",
"dev": true,
"requires": {
"graceful-fs": "^4.1.2",
"parse-json": "^4.0.0",
"pify": "^3.0.0",
"strip-bom": "^3.0.0"
}
},
"parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
"integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
"dev": true,
"requires": {
"error-ex": "^1.3.1",
"json-parse-better-errors": "^1.0.1"
}
},
"path-type": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
"integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==",
"dev": true,
"requires": {
"pify": "^3.0.0"
}
},
"pify": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
"integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
"dev": true
},
"read-pkg": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
"integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=",
"dev": true,
"requires": {
"load-json-file": "^4.0.0",
"normalize-package-data": "^2.3.2",
"path-type": "^3.0.0"
}
},
"strip-bom": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
"integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
"dev": true
}
}
},
"npm-run-path": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
@ -4579,8 +4720,7 @@
"oauth-sign": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
"dev": true
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
},
"object-assign": {
"version": "4.1.1",
@ -4923,8 +5063,7 @@
"performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
"dev": true
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
},
"picomatch": {
"version": "2.0.7",
@ -4932,6 +5071,12 @@
"integrity": "sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==",
"dev": true
},
"pidtree": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.0.tgz",
"integrity": "sha512-9CT4NFlDcosssyg8KVFltgokyKZIFjoBxw8CTGy+5F38Y1eQWrt8tRayiUOXE+zVKQnYu5BR8JjCtvK3BcnBhg==",
"dev": true
},
"pify": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
@ -5140,8 +5285,7 @@
"psl": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz",
"integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==",
"dev": true
"integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw=="
},
"public-encrypt": {
"version": "4.0.3",
@ -5198,8 +5342,7 @@
"qs": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
"dev": true
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
},
"query-string": {
"version": "4.3.4",
@ -5443,7 +5586,6 @@
"version": "2.88.0",
"resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
"integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
"dev": true,
"requires": {
"aws-sign2": "~0.7.0",
"aws4": "^1.8.0",
@ -5608,8 +5750,7 @@
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"sass-graph": {
"version": "2.2.4",
@ -5964,6 +6105,12 @@
"integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
"dev": true
},
"shell-quote": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz",
"integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==",
"dev": true
},
"signal-exit": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
@ -6195,7 +6342,6 @@
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
"integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
"dev": true,
"requires": {
"asn1": "~0.2.3",
"assert-plus": "^1.0.0",
@ -6421,6 +6567,17 @@
}
}
},
"string.prototype.padend": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.0.0.tgz",
"integrity": "sha1-86rvfBcZ8XDF6rHDK/eA2W4h8vA=",
"dev": true,
"requires": {
"define-properties": "^1.1.2",
"es-abstract": "^1.4.3",
"function-bind": "^1.0.2"
}
},
"string.prototype.trimleft": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz",
@ -6706,7 +6863,6 @@
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
"integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
"dev": true,
"requires": {
"psl": "^1.1.24",
"punycode": "^1.4.1"
@ -6715,8 +6871,7 @@
"punycode": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
"dev": true
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
}
}
},
@ -6854,7 +7009,6 @@
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
"integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
"dev": true,
"requires": {
"safe-buffer": "^5.0.1"
}
@ -6862,8 +7016,7 @@
"tweetnacl": {
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
"dev": true
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
},
"typedarray": {
"version": "0.0.6",
@ -7062,8 +7215,7 @@
"uuid": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz",
"integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==",
"dev": true
"integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ=="
},
"v8-compile-cache": {
"version": "2.0.3",
@ -7085,7 +7237,6 @@
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
"integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
"dev": true,
"requires": {
"assert-plus": "^1.0.0",
"core-util-is": "1.0.2",

13
package.json

@ -5,8 +5,10 @@
"private": true,
"scripts": {
"start": "node dist/server/index.js",
"build": "tsc",
"build:text-app": "webpack --config src/apps/text-app/composer/webpack.config.ts"
"build:server": "tsc",
"build:text-app": "webpack --config src/apps/text-app/composer/webpack.config.ts",
"build:gif-app": "webpack --config src/apps/gif-app/composer/webpack.config.ts",
"build": "run-s build:server build:text-app build:gif-app"
},
"devDependencies": {
"@types/classnames": "^2.2.9",
@ -14,12 +16,15 @@
"@types/mini-css-extract-plugin": "^0.8.0",
"@types/react": "^16.9.9",
"@types/react-dom": "^16.9.2",
"@types/request": "^2.48.3",
"@types/webpack": "^4.39.5",
"bulma": "^0.8.0",
"css-loader": "^3.2.0",
"file-loader": "^4.2.0",
"html-webpack-plugin": "^3.2.0",
"mini-css-extract-plugin": "^0.8.0",
"node-sass": "^4.12.0",
"npm-run-all": "^4.1.5",
"pino-pretty": "^3.2.2",
"sass-loader": "^8.0.0",
"style-loader": "^1.0.0",
@ -34,9 +39,11 @@
"@fortawesome/free-solid-svg-icons": "^5.11.2",
"@fortawesome/react-fontawesome": "^0.1.7",
"classnames": "^2.2.6",
"dotenv": "^8.2.0",
"fastify": "^2.10.0",
"fastify-static": "^2.5.0",
"react": "^16.10.2",
"react-dom": "^16.10.2"
"react-dom": "^16.10.2",
"request": "^2.88.0"
}
}

163
src/apps/gif-app/composer/app.tsx

@ -0,0 +1,163 @@
import React, { FC, useState, useEffect } from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faSearch } from '@fortawesome/free-solid-svg-icons'
import { Communicator } from '../../../communicator'
import classNames from 'classnames'
import Gif from './gif'
import { ClassDictionary, GiphyGif } from '../../../types'
const giphyIcon = require('./giphy.png')
import '../../../styles/default.scss'
type APIResponse = GiphyGif[]
interface Props {
communicator: Communicator
}
const useDebounce = (value: string, delay = 500) => {
const [valueD, setValueD] = useState(value)
useEffect(() => {
const handler = setTimeout(() => {
setValueD(value)
}, delay)
return () => {
clearTimeout(handler)
}
}, [value, delay])
return valueD
}
const App: FC<Props> = ({ communicator }) => {
const [posting, setPosting] = useState(false)
const [searching, setSearching] = useState(false)
const [search, setSearch] = useState('')
const searchD = useDebounce(search)
const [gifs, setGifs] = useState<GiphyGif[]>([])
const [selected, setSelected] = useState('')
const buttonStyle: ClassDictionary = {
'button': true,
'is-primary': true,
'is-loading': posting,
}
const controlStyle: ClassDictionary = {
'control': true,
'has-icons-left': true,
'is-loading': searching,
}
useEffect(() => {
const init = async () => {
try {
const content = await communicator.init()
if (content && content.parent && content.parent.data && content.parent.data.search) {
setSearch(content.parent.data.search)
}
await communicator.setHeight(1000)
} catch (err) {
console.error(err)
}
}
init()
}, [])
useEffect(() => {
const doTrending = async () => {
try {
const response = await fetch('/api/gifs/home')
setGifs(await response.json() as APIResponse)
} catch (err) {
console.error(err)
}
}
const doSearch = async () => {
try {
setSearching(true)
const response = await fetch(`/api/gifs/search?q=${search}`)
setGifs(await response.json() as APIResponse)
} catch (err) {
console.error(err)
}
setSearching(false)
}
if (!search) {
doTrending()
} else if (search.length > 2) {
doSearch()
}
}, [searchD])
const handleSearchChange = (search: string) => {
setSearch(search)
}
const post = async () => {
try {
const gif = gifs.find(g => g.id === selected)
if (!gif) return
setPosting(true)
await communicator.post({
attachments: [{
url: gif.images.fixed_height.url,
text: gif.title,
}],
data: {
search,
},
visible: true,
})
setSearch('')
setSelected('')
} catch (err) {
console.error(err)
}
setPosting(false)
}
return (
<div>
<div className="field">
<p className={classNames(controlStyle)}>
<input className="input" type="text" placeholder="Search" value={search} onChange={(e) => handleSearchChange(e.target.value)} />
<span className="icon is-small is-left">
<FontAwesomeIcon icon={faSearch} />
</span>
</p>
</div>
<div className="gifs is-flex">
{gifs.map(gif => <Gif key={gif.id} gif={gif} selected={gif.id === selected} onSelect={setSelected} />)}
</div>
<nav className="level">
<div className="level-left">
<div className="level-item">
<img src={giphyIcon} />
</div>
</div>
<div className="level-right">
<div className="level-item">
<button className={classNames(buttonStyle)} onClick={() => post()} disabled={!selected}>Post</button>
</div>
</div>
</nav>
</div>
)
}
export default App

24
src/apps/gif-app/composer/gif.tsx

@ -0,0 +1,24 @@
import React, { FC, useState, useEffect } from 'react'
import classNames from 'classnames'
import { ClassDictionary, GiphyGif } from '../../../types'
interface Props {
gif: GiphyGif
selected: boolean
onSelect: (id: string) => void
}
const Gif: FC<Props> = ({ gif, selected, onSelect }) => {
const classes: ClassDictionary = {
gif: true,
selected,
}
return (
<div className={classNames(classes)} onClick={() => onSelect(gif.id)}>
<img src={gif.images.fixed_height.url} alt={gif.title} />
</div>
)
}
export default Gif

BIN
src/apps/gif-app/composer/giphy.png

After

Width: 101  |  Height: 36  |  Size: 1.6 KiB

12
src/apps/gif-app/composer/index.ejs

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>GIF App</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div id="app"></div>
</body>
</html>

6
src/apps/gif-app/composer/index.tsx

@ -0,0 +1,6 @@
import React from 'react'
import { render } from 'react-dom'
import App from './app'
import { Communicator } from '../../../communicator'
render(<App communicator={new Communicator('e396113e76dffaa2ad6c')} />, document.getElementById('app'))

63
src/apps/gif-app/composer/webpack.config.ts

@ -0,0 +1,63 @@
import { resolve } from 'path'
import { Configuration } from 'webpack'
import HtmlWebpackPlugin from 'html-webpack-plugin'
import MiniCssExtractPlugin from 'mini-css-extract-plugin'
const config: Configuration = {
mode: 'development',
devtool: 'eval-source-map',
entry: {
app: resolve(__dirname, './index.tsx'),
},
output: {
path: resolve(__dirname, '../../../../dist/apps/gif-app/'),
filename: '[name].js',
},
optimization: {
splitChunks: {
chunks: 'all',
},
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.png'],
},
module: {
rules: [
{
test: /\.ts(x?)$/,
exclude: /node_modules/,
use: 'ts-loader',
},
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'sass-loader',
options: {
sourceMap: true,
},
},
],
},
{
test: /\.(jpe?g|gif|png|svg)$/,
use: ['file-loader'],
},
],
},
plugins: [
new HtmlWebpackPlugin({
title: 'GIF App',
hash: true,
template: resolve(__dirname, './index.ejs'),
filename: 'composer.html',
}),
new MiniCssExtractPlugin({
filename: '[name].css',
}),
],
}
export default config

17
src/apps/text-app/composer/app.tsx

@ -3,7 +3,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faEyeSlash } from '@fortawesome/free-solid-svg-icons'
import { Communicator } from '../../../communicator'
import classNames from 'classnames'
import { ClassDictionary } from '../../../types'
import { ClassDictionary, Post } from '../../../types'
import '../../../styles/default.scss'
@ -16,6 +16,7 @@ const App: FC<Props> = ({ communicator }) => {
const [content, setContent] = useState('')
const [cover, setCover] = useState('')
const [posting, setPosting] = useState(false)
const [parent, setParent] = useState<Post | null>(null)
const charactersLeft = maxCharacters - content.length
const showCharactersLeft = charactersLeft < (maxCharacters / 2)
@ -31,10 +32,14 @@ const App: FC<Props> = ({ communicator }) => {
'is-loading': posting,
}
const buttonText = parent ? 'Reply!' : 'Post!'
useEffect(() => {
const init = async () => {
try {
await communicator.init()
const response = await communicator.init()
if (response!.parent) setParent(response!.parent)
await communicator.setHeight(document.body.offsetHeight)
} catch (err) {
console.error('App Component: ', err)
@ -49,7 +54,11 @@ const App: FC<Props> = ({ communicator }) => {
try {
setPosting(true)
await communicator.post(true, content, cover)
await communicator.post({
text: content,
cover,
visible: true,
})
setContent('')
setCover('')
} catch (err) {
@ -85,7 +94,7 @@ const App: FC<Props> = ({ communicator }) => {
<div className="level-right">
<div className="level-item">
<button className={classNames(buttonStyle)} onClick={() => post()} disabled={buttonDisabled}>Post!</button>
<button className={classNames(buttonStyle)} onClick={() => post()} disabled={buttonDisabled}>{buttonText}</button>
</div>
</div>
</nav>

2
src/apps/text-app/composer/index.ejs

@ -2,7 +2,7 @@
<html>
<head>
<meta charset="utf-8">
<title>Flexor</title>
<title>Text App</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>

4
src/apps/text-app/composer/index.tsx

@ -3,6 +3,4 @@ import { render } from 'react-dom'
import App from './app'
import { Communicator } from '../../../communicator'
const communicator = new Communicator('1c28b52e4b1c93c0cc78')
render(<App communicator={communicator} />, document.getElementById('app'))
render(<App communicator={new Communicator('1c28b52e4b1c93c0cc78')} />, document.getElementById('app'))

1
src/apps/text-app/composer/webpack.config.ts

@ -11,7 +11,6 @@ const config: Configuration = {
},
output: {
path: resolve(__dirname, '../../../../dist/apps/text-app/'),
// publicPath: '/',
filename: '[name].js',
},
optimization: {

16
src/communicator/index.ts

@ -1,6 +1,9 @@
import { Post } from '../types'
export interface MessageContent {
[key: string]: any
height?: number
parent?: Post
}
export interface IncomingMessageData {
@ -26,9 +29,7 @@ interface ListenerCollection {
[name: string]: Listener
}
interface AppSettings {
[key: string]: any
}
type PostOptions = Post
export class Communicator {
private origin = 'http://localhost:8080'
@ -88,12 +89,7 @@ export class Communicator {
return this.postAndReceive('setHeight', { height })
}
async post(visible: boolean, text?: string, cover?: string, data?: object) {
return this.postAndReceive('post', {
visible,
text,
cover,
data,
})
async post(options: PostOptions) {
return this.postAndReceive('post', options)
}
}

145
src/server/api/index.ts

@ -0,0 +1,145 @@
import {
FastifyInstance,
RouteShorthandOptions,
Plugin,
DefaultQuery,
DefaultParams,
DefaultHeaders,
DefaultBody,
JSONSchema,
} from 'fastify'
import { Server, IncomingMessage, ServerResponse } from 'http'
import request from 'request'
import { PluginOptions, GiphyResponse } from '../../types'
interface FetchOptions {
url: string
params?: string[][]
method?: string
}
const paramsToString = (params: string[][]) => params.map(p => p.join('=')).join('&')
const giphyGifSchema: JSONSchema = {
type: 'object',
properties: {
type: { type: 'string' },
id: { type: 'string' },
slug: { type: 'string' },
url: { type: 'string' },
bitly_url: { type: 'string' },
embed_url: { type: 'string' },
title: { type: 'string' },
images: {
type: 'object',
properties: {
fixed_height: {
type: 'object',
properties: {
url: { type: 'string' },
},
},
fixed_height_still: {
type: 'object',
properties: {
url: { type: 'string' },
},
},
fixed_height_downsampled: {
type: 'object',
properties: {
url: { type: 'string' },
},
},
},
},
},
}
async function fetch<T = any>(options: FetchOptions) {
const { url, params = [], method = 'get' } = options
const uri = params.length > 0 ? `${url}?${paramsToString(params)}` : url
return new Promise<T>((resolve, reject) => {
request({
uri,
method,
json: true,
}, function(error, response, body) {
if (error) {
reject(error)
return
}
resolve(body)
})
})
}
function gifHomeRoute(server: FastifyInstance<Server, IncomingMessage, ServerResponse>) {
const options: RouteShorthandOptions = {
schema: {
response: {
200: {
type: 'array',
items: giphyGifSchema,
},
},
},
}
server.get<DefaultQuery, DefaultParams, DefaultHeaders, DefaultBody>('/api/gifs/home', options, async (request, reply) => {
const response = await fetch<GiphyResponse>({
url: `${process.env.GIPHY_API_ENDPOINT!}/v1/gifs/trending`,
params: [['api_key', process.env.GIPHY_API_KEY!], ['limit', '20']],
})
return response.data
})
}
function gifSearchRoute(server: FastifyInstance<Server, IncomingMessage, ServerResponse>) {
interface Query {
q: string
}
const options: RouteShorthandOptions = {
schema: {
querystring: {
type: 'object',
properties: {
q: { type: 'string' },
},
},
response: {
200: {
type: 'array',
items: giphyGifSchema,
},
},
},
}
server.get<Query, DefaultParams, DefaultHeaders, DefaultBody>('/api/gifs/search', options, async (request, reply) => {
const q = request.query.q ? request.query.q.trim() : ''
if (q.length < 3) {
reply.code(400)
return
}
const response = await fetch<GiphyResponse>({
url: `${process.env.GIPHY_API_ENDPOINT!}/v1/gifs/search`,
params: [['api_key', process.env.GIPHY_API_KEY!], ['limit', '20'], ['q', q]],
})
return response.data
})
}
const plugin: Plugin<Server, IncomingMessage, ServerResponse, PluginOptions> = async server => {
gifHomeRoute(server)
gifSearchRoute(server)
}
export default plugin

14
src/server/index.ts

@ -1,22 +1,28 @@
import { config } from 'dotenv'
import { resolve } from 'path'
import fastify from 'fastify'
import fastifyStatic from 'fastify-static'
const port = 8082
import api from './api'
config()
const server = fastify({
logger: {
level: 'info',
prettyPrint: true,
level: process.env.LOGGER_LEVEL,
prettyPrint: process.env.LOGGER_PRETTY_PRINT === 'true',
}
})
server.register(api)
server.register(fastifyStatic, {
root: resolve(__dirname, '..', 'apps'),
})
const start = async () => {
try {
await server.listen(port)
await server.listen(process.env.PORT!)
} catch (err) {
process.exit(1)
}

17
src/styles/default.scss

@ -31,3 +31,20 @@ $body-size: 14px;
body {
padding: 10px;
}
div.gifs {
display: flex;
flex-wrap: wrap;
}
div.gif {
margin: 5px;
img {
height: 100px;
}
}
.gif.selected {
border: solid 3px $green;
}

58
src/types/index.ts

@ -1,3 +1,61 @@
export interface ClassDictionary {
[name: string]: boolean
}
export interface PluginOptions {
}
export interface Attachment {
url: string
text?: string
cover?: string
}
export interface PostData {
[key: string]: any
}
export interface Post {
text?: string
cover?: string
attachments?: Attachment[]
data?: PostData
visible: boolean
}
export interface GiphyGif {
type: string
id: string
slug: string
url: string
bitly_url: string
embed_url: string
username: string
source: string
rating: string
content_url: string
title: string
images: {
fixed_height: {
url: string
}
fixed_height_still: {
url: string
}
fixed_height_downsampled: {
url: string
}
}
}
export interface GiphyPagination {
offset: number
total_count: number
count: number
}
export interface GiphyResponse {
data: GiphyGif[]
pagination: GiphyPagination
}

2
tsconfig.json

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

Loading…
Cancel
Save