From e6bbca1e5bfe64748be9e9be7b0ad1ec9f8e35c2 Mon Sep 17 00:00:00 2001 From: David Friederich Date: Mon, 8 Apr 2019 00:06:54 +0200 Subject: [PATCH 001/180] "ejected" from apollo-boost for local state management --- package-lock.json | 248 ++++++++++++++++++++++++++++++++----------- package.json | 19 +++- src/ApolloClient.tsx | 99 +++++++++++++++-- 3 files changed, 289 insertions(+), 77 deletions(-) diff --git a/package-lock.json b/package-lock.json index 643bb04..14f4053 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "admin-frontend", + "name": "vochabular-cms-frontend", "version": "0.1.0", "lockfileVersion": 1, "requires": true, @@ -1337,6 +1337,12 @@ "@types/unist": "*" } }, + "@types/yup": { + "version": "0.26.12", + "resolved": "https://registry.npmjs.org/@types/yup/-/yup-0.26.12.tgz", + "integrity": "sha512-lWCsvLer6G84Gj7yh+oFGRuGHsqZd1Dwu47CVVL0ATw+bOnGDgMNHbTn80p1onT66fvLfN8FnRA3eRANsnnbbQ==", + "dev": true + }, "@types/zen-observable": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.0.tgz", @@ -1899,18 +1905,18 @@ } }, "apollo-boost": { - "version": "0.1.28", - "resolved": "https://registry.npmjs.org/apollo-boost/-/apollo-boost-0.1.28.tgz", - "integrity": "sha512-WnOeFKyI+1FxWtIsIjqj5TC+xMxJyY1pw8e0Gyd99vKaGNNulx1+MBEy2qL3u7NiaGWj93vxu/y4r8tKNnNqyA==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/apollo-boost/-/apollo-boost-0.3.1.tgz", + "integrity": "sha512-VdXcTMxLBeNvANW/FtiarEkrRr/cepYKG6wTAURdy8CS33WYpEHtIg9S8tAjxwVzIECpE4lWyDKyPLoESJ072Q==", "requires": { - "apollo-cache": "^1.1.26", - "apollo-cache-inmemory": "^1.4.3", - "apollo-client": "^2.4.13", + "apollo-cache": "^1.2.1", + "apollo-cache-inmemory": "^1.5.1", + "apollo-client": "^2.5.1", "apollo-link": "^1.0.6", "apollo-link-error": "^1.0.3", "apollo-link-http": "^1.3.1", - "apollo-link-state": "^0.4.0", "graphql-tag": "^2.4.2", + "ts-invariant": "^0.2.1", "tslib": "^1.9.3" } }, @@ -1952,54 +1958,73 @@ } }, "apollo-link": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/apollo-link/-/apollo-link-1.2.8.tgz", - "integrity": "sha512-lfzGRxhK9RmiH3HPFi7TIEBhhDY9M5a2ZDnllcfy5QDk7cCQHQ1WQArcw1FK0g1B+mV4Kl72DSrlvZHZJEolrA==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/apollo-link/-/apollo-link-1.2.11.tgz", + "integrity": "sha512-PQvRCg13VduLy3X/0L79M6uOpTh5iHdxnxYuo8yL7sJlWybKRJwsv4IcRBJpMFbChOOaHY7Og9wgPo6DLKDKDA==", "requires": { - "zen-observable-ts": "^0.8.15" + "apollo-utilities": "^1.2.1", + "ts-invariant": "^0.3.2", + "tslib": "^1.9.3", + "zen-observable-ts": "^0.8.18" + }, + "dependencies": { + "ts-invariant": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.3.2.tgz", + "integrity": "sha512-QsY8BCaRnHiB5T6iE4DPlJMAKEG3gzMiUco9FEt1jUXQf0XP6zi0idT0i0rMTu8A326JqNSDsmlkA9dRSh1TRg==", + "requires": { + "tslib": "^1.9.3" + } + } } }, "apollo-link-dedup": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/apollo-link-dedup/-/apollo-link-dedup-1.0.15.tgz", - "integrity": "sha512-14/+Tg7ogcYVrvZa8C7uBQIvX2B/dCKSnojI41yDYGp/t2eWD5ITCWdgjhciXpi0Ij6z+NRyMEebACz3EOwm4w==", + "version": "1.0.18", + "resolved": "https://registry.npmjs.org/apollo-link-dedup/-/apollo-link-dedup-1.0.18.tgz", + "integrity": "sha512-1rr54wyMTuqUmbWvcXbwduIcaCDcuIgU6MqQ599nAMuTrbSOXthGfoAD8BDTxBGQ9roVlM7ABP0VZVEWRoHWSg==", "requires": { - "apollo-link": "^1.2.8" + "apollo-link": "^1.2.11", + "tslib": "^1.9.3" } }, "apollo-link-error": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/apollo-link-error/-/apollo-link-error-1.1.7.tgz", - "integrity": "sha512-olPTKr3yFoavFHSXSLqC5QSWrRACN8TK3+E0pVL8uVR0zILJflUSCRb8HizKQmxZWtr9yM+D2gRLu9mStI8qTA==", + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/apollo-link-error/-/apollo-link-error-1.1.10.tgz", + "integrity": "sha512-itG5UV7mQqaalmRkuRsF0cUS4zW2ja8XCbxkMZnIEeN24X3yoJi5hpJeAaEkXf0KgYNsR0+rmtCQNruWyxDnZQ==", "requires": { - "apollo-link": "^1.2.8", - "apollo-link-http-common": "^0.2.10" + "apollo-link": "^1.2.11", + "apollo-link-http-common": "^0.2.13", + "tslib": "^1.9.3" } }, "apollo-link-http": { - "version": "1.5.11", - "resolved": "https://registry.npmjs.org/apollo-link-http/-/apollo-link-http-1.5.11.tgz", - "integrity": "sha512-wDG+I9UmpfaZRPIvTYBgkvqiCgmz6yWgvuzW/S24Q4r4Xrfe6sLpg2FmarhtdP+hdN+IXTLbFNCZ+Trgfpifow==", + "version": "1.5.14", + "resolved": "https://registry.npmjs.org/apollo-link-http/-/apollo-link-http-1.5.14.tgz", + "integrity": "sha512-XEoPXmGpxFG3wioovgAlPXIarWaW4oWzt8YzjTYZ87R4R7d1A3wKR/KcvkdMV1m5G7YSAHcNkDLe/8hF2nH6cg==", "requires": { - "apollo-link": "^1.2.8", - "apollo-link-http-common": "^0.2.10" + "apollo-link": "^1.2.11", + "apollo-link-http-common": "^0.2.13", + "tslib": "^1.9.3" } }, "apollo-link-http-common": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/apollo-link-http-common/-/apollo-link-http-common-0.2.10.tgz", - "integrity": "sha512-KY9nhpAurw3z48OIYV0sCZFXrzWp/wjECsveK+Q9GUhhSe1kEbbUjFfmi+qigg+iELgdp5V8ioRJhinl1vPojw==", - "requires": { - "apollo-link": "^1.2.8" - } - }, - "apollo-link-state": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/apollo-link-state/-/apollo-link-state-0.4.2.tgz", - "integrity": "sha512-xMPcAfuiPVYXaLwC6oJFIZrKgV3GmdO31Ag2eufRoXpvT0AfJZjdaPB4450Nu9TslHRePN9A3quxNueILlQxlw==", + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/apollo-link-http-common/-/apollo-link-http-common-0.2.13.tgz", + "integrity": "sha512-Uyg1ECQpTTA691Fwx5e6Rc/6CPSu4TB4pQRTGIpwZ4l5JDOQ+812Wvi/e3IInmzOZpwx5YrrOfXrtN8BrsDXoA==", "requires": { - "apollo-utilities": "^1.0.8", - "graphql-anywhere": "^4.1.0-alpha.0" + "apollo-link": "^1.2.11", + "ts-invariant": "^0.3.2", + "tslib": "^1.9.3" + }, + "dependencies": { + "ts-invariant": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.3.2.tgz", + "integrity": "sha512-QsY8BCaRnHiB5T6iE4DPlJMAKEG3gzMiUco9FEt1jUXQf0XP6zi0idT0i0rMTu8A326JqNSDsmlkA9dRSh1TRg==", + "requires": { + "tslib": "^1.9.3" + } + } } }, "apollo-utilities": { @@ -4238,6 +4263,15 @@ "sha.js": "^2.4.8" } }, + "create-react-context": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/create-react-context/-/create-react-context-0.2.3.tgz", + "integrity": "sha512-CQBmD0+QGgTaxDL3OX1IDXYqjkp2It4RIbcb99jS6AEg27Ga+a9G3JtK6SIu0HBwPLZlmwt9F7UwWA4Bn92Rag==", + "requires": { + "fbjs": "^0.8.0", + "gud": "^1.0.0" + } + }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -6390,6 +6424,11 @@ "readable-stream": "^2.3.6" } }, + "fn-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fn-name/-/fn-name-2.0.1.tgz", + "integrity": "sha1-UhTXU3pNBqSjAcDMJi/rhBiAAuc=" + }, "follow-redirects": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.7.0.tgz", @@ -6708,6 +6747,39 @@ "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==" }, + "formik": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/formik/-/formik-1.5.2.tgz", + "integrity": "sha512-gVrNN4OmxvtwV4IvoyQJk797D3ptl3X8C1lJjbOBX48EFxxFTzchtUX5XiDSmhMIhtsg2rahT41VEbZPpgM6lQ==", + "requires": { + "create-react-context": "^0.2.2", + "deepmerge": "^2.1.1", + "hoist-non-react-statics": "^2.5.5", + "lodash": "^4.17.11", + "lodash-es": "^4.17.11", + "prop-types": "^15.6.1", + "react-fast-compare": "^2.0.1", + "tiny-warning": "^1.0.2", + "tslib": "^1.9.3" + }, + "dependencies": { + "deepmerge": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", + "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==" + }, + "hoist-non-react-statics": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", + "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" + } + } + }, + "formik-material-ui": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/formik-material-ui/-/formik-material-ui-0.0.16.tgz", + "integrity": "sha512-LVv7WG5o5VmKa2K1JUhr/NbCzgzlZrZXEUOXH57P44zVE+R8cOELy5U4RV7VU5lLQW4sq5nlBatW01WrqtfoDQ==" + }, "forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", @@ -7382,22 +7454,13 @@ "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" }, "graphql": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-14.1.1.tgz", - "integrity": "sha512-C5zDzLqvfPAgTtP8AUPIt9keDabrdRAqSWjj2OPRKrKxI9Fb65I36s1uCs1UUBFnSWTdO7hyHi7z1ZbwKMKF6Q==", + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-14.2.1.tgz", + "integrity": "sha512-2PL1UbvKeSjy/lUeJqHk+eR9CvuErXoCNwJI4jm3oNFEeY+9ELqHNKO1ZuSxAkasPkpWbmT/iMRMFxd3cEL3tQ==", "requires": { "iterall": "^1.2.2" } }, - "graphql-anywhere": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/graphql-anywhere/-/graphql-anywhere-4.2.1.tgz", - "integrity": "sha512-4zlzTFzixGXtIYjX7BiXQOGhQ5yQVohj/EKNxUHUTAR7lHnCmrXU17gGtZ+108l9TkoHNfc33ieJ9U8trnHE1w==", - "requires": { - "apollo-utilities": "^1.2.1", - "tslib": "^1.9.3" - } - }, "graphql-tag": { "version": "2.10.1", "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.10.1.tgz", @@ -7408,6 +7471,11 @@ "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=" }, + "gud": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz", + "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==" + }, "gzip-size": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.0.0.tgz", @@ -9701,6 +9769,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" }, + "lodash-es": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.11.tgz", + "integrity": "sha512-DHb1ub+rMjjrxqlB3H56/6MXtm1lSksDp2rA2cNWjG8mlDUYFhUj3Di2Zn5IwSU87xLv8tNIQ7sSwE/YOX/D/Q==" + }, "lodash._reinterpolate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", @@ -13200,6 +13273,11 @@ "react-is": "^16.8.1" } }, + "property-expr": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-1.5.1.tgz", + "integrity": "sha512-CGuc0VUTGthpJXL36ydB6jnbyOf/rAHFvmVrJlH+Rg0DqqLFQGAP6hIaxD/G0OAmBJPhXDHuEJigrp0e0wFV6g==" + }, "property-information": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.0.1.tgz", @@ -13395,22 +13473,32 @@ } }, "react-apollo": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/react-apollo/-/react-apollo-2.5.1.tgz", - "integrity": "sha512-obXPcmjJ5O75UkoizcsIbe0PXAXKRqm+SwNu059HQInB3FAab9PIn4wd/KglpPNiB9JXHe8OJDov0lFMcBbHEQ==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/react-apollo/-/react-apollo-2.5.4.tgz", + "integrity": "sha512-olH9zYijOXVfj14hD7bQlZ0POBJchxg2e+mfnxEiEdqZra4+58SfIY0KPhmM9jqbeeusc6J/P4zzWIHt5DdNDg==", "requires": { "apollo-utilities": "^1.2.1", - "hoist-non-react-statics": "^3.0.0", + "hoist-non-react-statics": "^3.3.0", "lodash.isequal": "^4.5.0", - "prop-types": "^15.6.0", - "ts-invariant": "^0.2.1", + "prop-types": "^15.7.2", + "ts-invariant": "^0.3.2", "tslib": "^1.9.3" + }, + "dependencies": { + "ts-invariant": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.3.2.tgz", + "integrity": "sha512-QsY8BCaRnHiB5T6iE4DPlJMAKEG3gzMiUco9FEt1jUXQf0XP6zi0idT0i0rMTu8A326JqNSDsmlkA9dRSh1TRg==", + "requires": { + "tslib": "^1.9.3" + } + } } }, "react-apollo-hooks": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/react-apollo-hooks/-/react-apollo-hooks-0.4.3.tgz", - "integrity": "sha512-mRFPb7bk42wOlGnIdwLc4XO43uTczdx7CzpdTmNwjzAgb/H/nKEXeZitoF1Wgtj0Zb6R5wTGs2nBIMfz0/DzEA==", + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/react-apollo-hooks/-/react-apollo-hooks-0.4.5.tgz", + "integrity": "sha512-fq04h88hg4ONtlzxYInmv7Okqqstzk3UCp55jHWOlIO2lFKoZPhQrtCz7A+TrcRu8GGwtG1SsioRKN8kIQaAOQ==", "requires": { "lodash": "^4.17.11" } @@ -13583,6 +13671,11 @@ "warning": "^4.0.1" } }, + "react-fast-compare": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", + "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==" + }, "react-i18next": { "version": "10.3.0", "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-10.3.0.tgz", @@ -15654,6 +15747,11 @@ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz", "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=" }, + "synchronous-promise": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/synchronous-promise/-/synchronous-promise-2.0.7.tgz", + "integrity": "sha512-16GbgwTmFMYFyQMLvtQjvNWh30dsFe1cAW5Fg1wm5+dg84L9Pe36mftsIRU95/W2YsISxsz/xq4VB23sqpgb/A==" + }, "table": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/table/-/table-5.2.3.tgz", @@ -15802,6 +15900,11 @@ "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" }, + "tiny-warning": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.2.tgz", + "integrity": "sha512-rru86D9CpQRLvsFG5XFdy0KdLAvjdQDyZCsRcuu60WtzFylDM3eAWSxEVz5kzL2Gp544XiUvPbVKtOA/txLi9Q==" + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -15871,6 +15974,11 @@ "hoek": "4.x.x" } }, + "toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=" + }, "tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", @@ -17329,16 +17437,30 @@ } } }, + "yup": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/yup/-/yup-0.27.0.tgz", + "integrity": "sha512-v1yFnE4+u9za42gG/b/081E7uNW9mUj3qtkmelLbW5YPROZzSH/KUUyJu9Wt8vxFJcT9otL/eZopS0YK1L5yPQ==", + "requires": { + "@babel/runtime": "^7.0.0", + "fn-name": "~2.0.1", + "lodash": "^4.17.11", + "property-expr": "^1.5.0", + "synchronous-promise": "^2.0.6", + "toposort": "^2.0.2" + } + }, "zen-observable": { "version": "0.8.13", "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.13.tgz", "integrity": "sha512-fa+6aDUVvavYsefZw0zaZ/v3ckEtMgCFi30sn91SEZea4y/6jQp05E3omjkX91zV6RVdn15fqnFZ6RKjRGbp2g==" }, "zen-observable-ts": { - "version": "0.8.15", - "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-0.8.15.tgz", - "integrity": "sha512-sXKPWiw6JszNEkRv5dQ+lQCttyjHM2Iks74QU5NP8mMPS/NrzTlHDr780gf/wOBqmHkPO6NCLMlsa+fAQ8VE8w==", + "version": "0.8.18", + "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-0.8.18.tgz", + "integrity": "sha512-q7d05s75Rn1j39U5Oapg3HI2wzriVwERVo4N7uFGpIYuHB9ff02P/E92P9B8T7QVC93jCMHpbXH7X0eVR5LA7A==", "requires": { + "tslib": "^1.9.3", "zen-observable": "^0.8.0" } } diff --git a/package.json b/package.json index e75365d..40051f1 100644 --- a/package.json +++ b/package.json @@ -7,20 +7,28 @@ "@material-ui/icons": "^3.0.2", "@sentry/browser": "^4.6.6", "@sentry/cli": "^1.40.0", - "apollo-boost": "^0.1.28", + "apollo-boost": "^0.3.1", + "apollo-cache-inmemory": "^1.5.1", + "apollo-client": "^2.5.1", + "apollo-link": "^1.2.11", + "apollo-link-error": "^1.1.10", + "apollo-link-http": "^1.5.14", "auth0-js": "^9.10.0", "classnames": "^2.2.6", "dotenv": "^7.0.0", - "graphql": "^14.1.1", + "formik": "^1.5.2", + "formik-material-ui": "0.0.16", + "graphql": "^14.2.1", "i18next": "^15.0.6", "i18next-browser-languagedetector": "^3.0.1", "react": "^16.8.3", - "react-apollo": "^2.4.1", - "react-apollo-hooks": "^0.4.1", + "react-apollo": "^2.5.4", + "react-apollo-hooks": "^0.4.5", "react-dom": "^16.8.3", "react-i18next": "^10.3.0", "react-router-dom": "^4.3.1", - "react-scripts": "^2.1.8" + "react-scripts": "^2.1.8", + "yup": "^0.27.0" }, "scripts": { "start": "react-scripts start", @@ -45,6 +53,7 @@ "@types/react": "^16.8.4", "@types/react-dom": "^16.8.2", "@types/react-router-dom": "^4.3.1", + "@types/yup": "^0.26.12", "typedoc": "^0.14.2", "typescript": "^3.3.3333" } diff --git a/src/ApolloClient.tsx b/src/ApolloClient.tsx index 27ca24b..5f13a61 100644 --- a/src/ApolloClient.tsx +++ b/src/ApolloClient.tsx @@ -1,14 +1,95 @@ -import ApolloClient from "apollo-boost"; +import { ApolloClient } from "apollo-client"; +import { InMemoryCache } from "apollo-cache-inmemory"; +import { HttpLink } from "apollo-link-http"; +import { onError } from "apollo-link-error"; +import { ApolloLink, Observable } from "apollo-link"; + import auth0Client from "./auth/Auth"; +import i18n from "./i18n"; +import { typeDefs, resolvers } from "./queries/resolvers"; + +// Setup the cache +const cache = new InMemoryCache({}); + +// On each request, set the current idToken in the header +const request = async (operation: any) => { + operation.setContext({ + headers: { + authorization: "JWT " + auth0Client.getIdToken() + } + }); +}; -export default new ApolloClient({ - uri: process.env.REACT_APP_BACKEND_URL, - // On each request, set the authorization header - request: async operation => { - operation.setContext({ - headers: { - authorization: 'JWT ' + auth0Client.getIdToken() +// Here we can chain +const requestLink = new ApolloLink( + (operation, forward) => + new Observable(observer => { + let handle: any; + Promise.resolve(operation) + .then(oper => request(oper)) + .then(() => { + if (!forward) return null; + handle = forward(operation).subscribe({ + next: observer.next.bind(observer), + error: observer.error.bind(observer), + complete: observer.complete.bind(observer) + }); + }) + .catch(observer.error.bind(observer)); + + return () => { + if (handle) handle.unsubscribe(); + }; + }) +); + +const client = new ApolloClient({ + cache, + link: ApolloLink.from([ + onError(({ graphQLErrors, networkError }) => { + if (graphQLErrors) { + // TODO: We should send something to sentry... + // sendToLoggingService(graphQLErrors); + } + if (networkError) { + // TODO: We should logout the user, if the network error is actually a 400 or something... + // handleNetworkError / auth0Client.logout(); } - }); + }), + requestLink, + new HttpLink({ + uri: process.env.REACT_APP_BACKEND_URL + }) + ]), + typeDefs: typeDefs, + resolvers: resolvers +}); + +// These are the default values, if nothing is set in localStorage/the backend +const defaultSettings = { + __typename: "Settings", + userName: "", + currentRole: (auth0Client && auth0Client.getCurrentRole()) || "viewer", + language: i18n.language, + translatorLanguages: [], + receiveEventNotifications: false, + hasCompletedSetup: false +}; + +// Get the initial state for settings +const settings = JSON.parse( + localStorage.getItem("settings") || JSON.stringify(defaultSettings) +); + +/** + * Write the default/initial values to the cache + */ +client.cache.writeData({ + data: { + settings: { + ...settings + } } }); + +export default client; From 1b683cea0873c9171f738db08b1bb985e1d05b54 Mon Sep 17 00:00:00 2001 From: David Friederich Date: Mon, 8 Apr 2019 00:09:03 +0200 Subject: [PATCH 002/180] add currentRole to authClient --- src/auth/Auth.ts | 339 ++++++++++++++++++------------- src/components/ErrorBoundary.tsx | 60 +++--- src/index.tsx | 2 +- 3 files changed, 227 insertions(+), 174 deletions(-) diff --git a/src/auth/Auth.ts b/src/auth/Auth.ts index 04722f4..69ed431 100644 --- a/src/auth/Auth.ts +++ b/src/auth/Auth.ts @@ -1,160 +1,215 @@ import auth0, { - Auth0DecodedHash, - Auth0Error, - Auth0ParseHashError, Auth0UserProfile + Auth0DecodedHash, + Auth0Error, + Auth0ParseHashError, + Auth0UserProfile } from "auth0-js"; import client from "src/ApolloClient"; -import {AUTH_CONFIG} from "./auth0-variables"; +import { AUTH_CONFIG } from "./auth0-variables"; import history from "../history"; +import { GET_SETTINGS } from "src/queries/settings"; /** * This class provides methods for authorization with OAuth provider "Auth0" * [[include:authentication.md]] */ class Auth { - accessToken: string | undefined; - idToken: string | undefined; - expiresAt: number; - auth0: auth0.WebAuth; - userProfile: Auth0UserProfile | null; - - constructor() { - (this.accessToken = undefined), - (this.idToken = undefined), - (this.userProfile = null), - (this.expiresAt = 0), - (this.auth0 = new auth0.WebAuth({ - domain: AUTH_CONFIG.domain || "", - clientID: AUTH_CONFIG.clientId || "", - redirectUri: AUTH_CONFIG.callbackUrl, - responseType: "token id_token", - scope: "openid profile email" - })); - this.getProfile = this.getProfile.bind(this); - } - - getProfile(callback: Function) { - if (this.accessToken != null) { - this.auth0.client.userInfo(this.accessToken, (err, profile) => { - if (profile) { - this.userProfile = profile; - } - callback(err, profile); - }); + accessToken: string | undefined; + idToken: string | undefined; + expiresAt: number; + auth0: auth0.WebAuth; + userProfile: Auth0UserProfile | null; + allowedRoles: string[]; + currentRole: string | undefined; + + constructor() { + (this.accessToken = undefined), + (this.idToken = undefined), + (this.userProfile = null), + (this.expiresAt = 0), + (this.auth0 = new auth0.WebAuth({ + domain: AUTH_CONFIG.domain || "", + clientID: AUTH_CONFIG.clientId || "", + redirectUri: AUTH_CONFIG.callbackUrl, + responseType: "token id_token", + scope: "openid profile email" + })); + (this.getProfile = this.getProfile.bind(this)), + (this.allowedRoles = []), + (this.currentRole = undefined); + } + + getProfile(callback: Function) { + if (this.accessToken != null) { + this.auth0.client.userInfo(this.accessToken, (err, profile) => { + if (profile) { + this.userProfile = profile; } + callback(err, profile); + }); } - - /** - * Attempts to authorize a user. If the user is already "known", Auth0 Silent Auth kicks in: - * https://auth0.com/docs/api-auth/tutorials/silent-authentication - */ - public login = (): void => { - // Need this prompt, as otherwise Auth0 Silent-Login kicks in... - this.auth0.authorize({ - prompt: "login" - }); - }; - - /** - * Parses the auth result consisting of 2 tokens and if successful, sets the "session". - * On error, redirects and sends notification - */ - public handleAuthentication = (): void => { - // TODO: How to type this properly? This didn't work: ` - // `this.auth0.parseHash((err: Auth0Error, authResult: Auth0DecodedHash)...` - this.auth0.parseHash((err, authResult) => { - if (authResult && authResult.accessToken && authResult.idToken) { - this.setSession(authResult); - // navigate to the home route - history.push("/"); - } else if (err) { - history.replace("/"); - console.error(err); - alert(`Error: ${err.error}. Check the console for further details.`); - } - }); - }; - - /** - * @returns Return the access token used for authentication of requests. - */ - public getAccessToken = (): string | undefined => { - return this.accessToken; - }; - - /** - * @returns Return the id token used for refreshing access tokens. - */ - public getIdToken = (): string | undefined => { - return this.idToken; - }; - - /** - * @returns Return current user's roles. - */ - public getRoles = (): Array => { - // TODO: Implement role retrieval logic - return ["user"]; - }; - - /** - * Updates the current session with a new access token. On error, - */ - public renewSession = async (): Promise => { - return new Promise((resolve, reject) => - this.auth0.checkSession({}, (err, authResult) => { - if (authResult && authResult.accessToken && authResult.idToken) { - this.setSession(authResult); - resolve(); - } else if (err) { - this.logout(); - reject(err); - } - }) - ); - }; - - public logout = (): void => { - // Remove tokens and expiry time - this.accessToken = undefined; - this.idToken = undefined; - this.expiresAt = 0; - this.userProfile = null; - - // Remove isLoggedIn flag from localStorage - localStorage.removeItem("isLoggedIn"); - + } + + /** + * Attempts to authorize a user. If the user is already "known", Auth0 Silent Auth kicks in: + * https://auth0.com/docs/api-auth/tutorials/silent-authentication + */ + public login = (): void => { + // Need this prompt, as otherwise Auth0 Silent-Login kicks in... + this.auth0.authorize({ + prompt: "login" + }); + }; + + /** + * Parses the auth result consisting of 2 tokens and if successful, sets the "session". + * On error, redirects and sends notification + */ + public handleAuthentication = (): void => { + // TODO: How to type this properly? This didn't work: ` + // `this.auth0.parseHash((err: Auth0Error, authResult: Auth0DecodedHash)...` + this.auth0.parseHash((err, authResult) => { + if (authResult && authResult.accessToken && authResult.idToken) { + this.setSession(authResult); // navigate to the home route + history.push("/"); + } else if (err) { history.replace("/"); - client.resetStore(); - }; - - /** - * Checks wether the access token is still valid (not expired) - * @returns A boolean wether the access token is still valid - */ - public isAuthenticated = (): boolean => { - // Check whether the current time is past the - // access token's expiry time - let expiresAt = this.expiresAt; - return new Date().getTime() < expiresAt; - }; - - /** - * - * @param authResult - */ - private setSession = (authResult: Auth0DecodedHash): void => { - // Set isLoggedIn flag in localStorage - localStorage.setItem("isLoggedIn", "true"); - - // Set the time that the access token will expire at - let expiresAt = authResult.expiresIn! * 1000 + new Date().getTime(); - this.accessToken = authResult.accessToken; - this.idToken = authResult.idToken; - this.expiresAt = expiresAt; - }; + console.error(err); + alert(`Error: ${err.error}. Check the console for further details.`); + } + }); + }; + + /** + * @returns Return the access token used for authentication of requests. + */ + public getAccessToken = (): string | undefined => { + return this.accessToken; + }; + + /** + * @returns Return the id token used for refreshing access tokens. + */ + public getIdToken = (): string | undefined => { + return this.idToken; + }; + + /** + * @returns Return current user's allowed roles. + */ + public getAllowedRoles = (): string[] => { + return this.allowedRoles; + }; + + /** + * @returns Return the current set role of the current user... + */ + public getCurrentRole = (): string | undefined => { + return this.currentRole; + }; + + /** + * Changes the current role of a user + */ + public changeCurrentRole = (newRole: string): void => { + if (this.allowedRoles.includes(newRole)) { + this.currentRole = newRole; + const data: any = client.readQuery({ query: GET_SETTINGS }); + // TODO: Should reset the store smartly. but not that we end up in a loop! + client.writeData({ + data: { settings: { ...data.settings, currentRole: newRole } } + }); + } else { + throw new Error( + `Error: The role ${newRole} is not part of the allowed roles ${ + this.allowedRoles + }` + ); + } + }; + + /** + * Updates the current session with a new access token. On error, + */ + public renewSession = async (): Promise => { + return new Promise((resolve, reject) => + this.auth0.checkSession({}, (err, authResult) => { + if (authResult && authResult.accessToken && authResult.idToken) { + this.setSession(authResult); + resolve(); + } else if (err) { + this.logout(); + reject(err); + } + }) + ); + }; + + public logout = (): void => { + // Remove tokens and expiry time + this.accessToken = undefined; + this.idToken = undefined; + this.expiresAt = 0; + this.userProfile = null; + + // Remove isLoggedIn flag from localStorage + localStorage.removeItem("isLoggedIn"); + + // navigate to the home route + history.replace("/"); + client.resetStore(); + }; + + /** + * Checks wether the access token is still valid (not expired) + * @returns A boolean wether the access token is still valid + */ + public isAuthenticated = (): boolean => { + // Check whether the current time is past the + // access token's expiry time + let expiresAt = this.expiresAt; + return new Date().getTime() < expiresAt; + }; + + /** + * A user can have multiple roles, e.g. he can choose which role he currently wants. + */ + private updateRoles = (idTokenPayload: any): void => { + try { + const namespace = "https://admin.vochabular.ch/jwt/claims"; + const allowedRoles = idTokenPayload[namespace]["x-allowed-roles"]; + const defaultRole = idTokenPayload[namespace]["x-default-role"]; + + this.allowedRoles = allowedRoles; + + // Set current role to default role if not set or if not anymore part of roles + if (!this.currentRole || !allowedRoles.includes(this.currentRole)) { + this.changeCurrentRole(defaultRole); + } + } catch (e) { + this.allowedRoles = []; + } + }; + + /** + * + * @param authResult + */ + private setSession = (authResult: Auth0DecodedHash): void => { + // Set isLoggedIn flag in localStorage + localStorage.setItem("isLoggedIn", "true"); + + // Set the time that the access token will expire at + let expiresAt = authResult.expiresIn! * 1000 + new Date().getTime(); + this.accessToken = authResult.accessToken; + this.idToken = authResult.idToken; + this.expiresAt = expiresAt; + + // Update the roles in memory + this.updateRoles(authResult.idTokenPayload); + }; } const auth0Client = new Auth(); diff --git a/src/components/ErrorBoundary.tsx b/src/components/ErrorBoundary.tsx index 738359d..df1f959 100644 --- a/src/components/ErrorBoundary.tsx +++ b/src/components/ErrorBoundary.tsx @@ -1,45 +1,43 @@ import * as React from "react"; import * as Sentry from "@sentry/browser"; import auth0Client from "src/auth/Auth"; -import {Auth0Error, Auth0UserProfile} from "auth0-js"; +import { Auth0Error, Auth0UserProfile } from "auth0-js"; type props = {}; type state = { error: boolean }; class ErrorBoundary extends React.Component { - constructor(props: any) { - super(props); - this.state = {error: false}; - } + constructor(props: any) { + super(props); + this.state = { error: false }; + } - componentDidCatch(error: Error, errorInfo: any) { - this.setState({error: true}); - auth0Client.getProfile((error: Auth0Error, profile: Auth0UserProfile) => { - Sentry.withScope(scope => { - scope.setUser({ - "email": profile.email, - "username": profile.nickname - }); - scope.setExtra("user_role", auth0Client.getRoles()); - Object.keys(errorInfo).forEach(key => { - scope.setExtra(key, errorInfo[key]); - }); - Sentry.captureException(error); - }); + componentDidCatch(error: Error, errorInfo: any) { + this.setState({ error: true }); + auth0Client.getProfile((error: Auth0Error, profile: Auth0UserProfile) => { + Sentry.withScope(scope => { + scope.setUser({ + email: profile.email, + username: profile.nickname }); - } + scope.setExtra("user_role", auth0Client.getCurrentRole()); + Object.keys(errorInfo).forEach(key => { + scope.setExtra(key, errorInfo[key]); + }); + Sentry.captureException(error); + }); + }); + } - render() { - if (this.state.error) { - //render fallback UI - return ( - Sentry.showReportDialog()}>Report feedback - ); - } else { - //when there's not an error, render children untouched - return this.props.children; - } + render() { + if (this.state.error) { + //render fallback UI + return Sentry.showReportDialog()}>Report feedback; + } else { + //when there's not an error, render children untouched + return this.props.children; } + } } -export default (ErrorBoundary) \ No newline at end of file +export default ErrorBoundary; diff --git a/src/index.tsx b/src/index.tsx index 2d32c8e..14d012a 100755 --- a/src/index.tsx +++ b/src/index.tsx @@ -19,7 +19,7 @@ Sentry.init({ dsn: process.env.REACT_APP_SENTRY_URL, environment: process.env.NODE_ENV, release: `${packageJson.name}@${packageJson.version}`, - debug: process.env.NODE_ENV === "development" + debug: false // process.env.NODE_ENV === "development" }); ReactDOM.render( From 80889705cd3e61b7bb44badab348c81a5404b4bd Mon Sep 17 00:00:00 2001 From: David Friederich Date: Mon, 8 Apr 2019 00:10:22 +0200 Subject: [PATCH 003/180] add local resolver & query for settings (for now) --- src/PrivateApp.tsx | 64 ++++++++++++++++++------------ src/components/AppBar.tsx | 30 +++++++------- src/components/BusyOrErrorCard.tsx | 2 +- src/queries/resolvers.ts | 43 ++++++++++++++++++++ src/queries/settings.ts | 27 +++++++++++++ src/styles.ts | 4 ++ 6 files changed, 130 insertions(+), 40 deletions(-) create mode 100644 src/queries/resolvers.ts create mode 100644 src/queries/settings.ts diff --git a/src/PrivateApp.tsx b/src/PrivateApp.tsx index b59313c..df82967 100644 --- a/src/PrivateApp.tsx +++ b/src/PrivateApp.tsx @@ -1,27 +1,20 @@ import React, { Fragment, useState } from "react"; -import classNames from "classnames"; import { Switch, Route } from "react-router"; import { withStyles, WithStyles } from "@material-ui/core/styles"; -import List from "@material-ui/core/List"; -import Typography from "@material-ui/core/Typography"; -import Divider from "@material-ui/core/Divider"; -import IconButton from "@material-ui/core/IconButton"; -import ChevronLeftIcon from "@material-ui/icons/ChevronLeft"; -import Toolbar from "@material-ui/core/Toolbar"; -import Badge from "@material-ui/core/Badge"; -import MenuIcon from "@material-ui/icons/Menu"; -import NotificationsIcon from "@material-ui/icons/Notifications"; -import PowerIcon from "@material-ui/icons/PowerSettingsNew"; import auth0Client from "src/auth/Auth"; import { styles } from "src/styles"; import { getAllAccessibleRoutes } from "src/privateRoutes"; import NotFound404 from "src/components/404"; import AppBar from "src/components/AppBar"; -import Settings from "src/pages/Settings/Settings"; import Drawer from "src/components/Drawer"; import useToggle from "src/hooks/useToggle"; +import SetupWizard from "./pages/SetupWizard/SetupWizard"; +import { Typography } from "@material-ui/core"; +import { GET_SETTINGS } from "./queries/settings"; +import { useQuery } from "react-apollo-hooks"; +import BusyOrErrorCard from "./components/BusyOrErrorCard"; // TODO: We should have the AppBar in a own component. However, that messes up the layout... // import AppBar from "../components/AppBar"; @@ -32,25 +25,46 @@ const PrivateApp: React.FunctionComponent = ({ classes }) => { const { toggler: isDrawerOpen, handleToggler: toggleDrawer } = useToggle( false ); + + // TODO: Need to actually get the current role from auth0Client. Via a setting to force a rerender? const accessibleRoutes = getAllAccessibleRoutes("admin", false); + // TODO: Currently, we get the data from the local Apollo cache/state (notice the @client directive in the query!). Later we have to fetch this from the backend! + const { data, error, loading } = useQuery(GET_SETTINGS); + + const hasCompletedSetup = + data && data.settings && data.settings.hasCompletedSetup; + return (
- - + + {!hasCompletedSetup ? null : ( + + )}
- - {accessibleRoutes.map((r: any) => ( - - ))} - - + + {loading || error ? ( + + ) : !hasCompletedSetup ? ( + + ) : ( + + {accessibleRoutes.map((r: any) => ( + + ))} + + + )}
); diff --git a/src/components/AppBar.tsx b/src/components/AppBar.tsx index 2146c9d..236efd0 100644 --- a/src/components/AppBar.tsx +++ b/src/components/AppBar.tsx @@ -17,7 +17,6 @@ import MenuIcon from "@material-ui/icons/Menu"; import NotificationsIcon from "@material-ui/icons/Notifications"; import PowerIcon from "@material-ui/icons/PowerSettingsNew"; - import auth0Client from "src/auth/Auth"; import { styles } from "src/styles"; import { getAllAccessibleRoutes } from "src/privateRoutes"; @@ -26,12 +25,13 @@ import { getAllAccessibleRoutes } from "src/privateRoutes"; // import AppBar from "../components/AppBar"; interface Props extends WithStyles { + isDrawerDeactivated?: boolean; isDrawerOpen: boolean; toggleDrawer: any; } function AppBar(props: Props) { - const { classes, isDrawerOpen, toggleDrawer } = props; + const { classes, isDrawerOpen, toggleDrawer, isDrawerDeactivated } = props; return ( - toggleDrawer(!isDrawerOpen)} - className={classNames( - classes.menuButton, - isDrawerOpen && classes.menuButtonHidden - )} - > - - + {isDrawerDeactivated ? null : ( + toggleDrawer(!isDrawerOpen)} + className={classNames( + classes.menuButton, + isDrawerOpen && classes.menuButtonHidden + )} + > + + + )} { error?: ApolloError; loading: boolean; - noResults: boolean; + noResults?: boolean; } const BusyOrErrorCard: React.FunctionComponent = ({ diff --git a/src/queries/resolvers.ts b/src/queries/resolvers.ts new file mode 100644 index 0000000..9603dd2 --- /dev/null +++ b/src/queries/resolvers.ts @@ -0,0 +1,43 @@ +import gql from "graphql-tag"; + +import { GET_SETTINGS } from "./settings"; +import auth0Client from "src/auth/Auth"; +import i18n from "src/i18n"; + +export const typeDefs = gql` + extend type Query { + settings: Settings! + } + + extend type Settings { + userName: string + currentRole: string + language: string + translatorLanguages: [string] + receiveEventNotifications: boolean + hasCompletedSetup: boolean + } + + extend type Mutation { + updateSettings(settings: Settings): Settings + } +`; + +export const resolvers = { + Query: { + getSettings: (_: any, params: any, { cache }: any) => { + cache.readQuery({ query: GET_SETTINGS }); + } + }, + Mutation: { + updateSettings: (_: any, { settings }: any, { cache }: any) => { + // TODO: Set current role and set language + + cache.writeQuery({ + query: GET_SETTINGS, + data: { settings: { ...settings } } + }); + return settings; + } + } +}; diff --git a/src/queries/settings.ts b/src/queries/settings.ts new file mode 100644 index 0000000..3ec3168 --- /dev/null +++ b/src/queries/settings.ts @@ -0,0 +1,27 @@ +import gql from "graphql-tag"; + +export const GET_SETTINGS = gql` + query getSettings { + settings @client { + userName + language + translatorLanguages + receiveEventNotifications + currentRole + hasCompletedSetup + } + } +`; + +export const UPDATE_SETTINGS = gql` + mutation updateSettings($settings: Settings) { + updateSettings(settings: $settings) @client { + userName + currentRole + language + translatorLanguages + receiveEventNotifications + hasCompletedSetup + } + } +`; diff --git a/src/styles.ts b/src/styles.ts index 89d2ca4..91532f3 100644 --- a/src/styles.ts +++ b/src/styles.ts @@ -13,6 +13,7 @@ https://material-ui.com/guides/typescript/ export const styles = (theme: Theme) => createStyles({ root: { + flexGrow: 1, display: "flex" }, container: { @@ -21,6 +22,9 @@ export const styles = (theme: Theme) => justifyContent: "center", alignItems: "center" }, + fullWidthContainer: { + width: "100%" + }, card: { display: "flex", flexDirection: "column", From bd59ac946b2064a5753bb7acd185a2d83f2616c7 Mon Sep 17 00:00:00 2001 From: David Friederich Date: Mon, 8 Apr 2019 00:10:50 +0200 Subject: [PATCH 004/180] Setting wizard + setting screen in sections --- src/configuration.json | 8 ++ src/locales/de-CH.json | 18 ++- src/locales/en-US.json | 29 +++- src/pages/Settings/Settings.tsx | 77 +++++++--- .../SettingsSection/GeneralSection.tsx | 72 ++++++++++ .../SettingsSection/LanguagesSection.tsx | 70 ++++++++++ .../SettingsSection/NotificationSection.tsx | 56 ++++++++ src/pages/SetupWizard/SetupWizard.tsx | 132 ++++++++++++++++++ 8 files changed, 436 insertions(+), 26 deletions(-) create mode 100644 src/configuration.json create mode 100644 src/pages/Settings/SettingsSection/GeneralSection.tsx create mode 100644 src/pages/Settings/SettingsSection/LanguagesSection.tsx create mode 100644 src/pages/Settings/SettingsSection/NotificationSection.tsx create mode 100644 src/pages/SetupWizard/SetupWizard.tsx diff --git a/src/configuration.json b/src/configuration.json new file mode 100644 index 0000000..b3763df --- /dev/null +++ b/src/configuration.json @@ -0,0 +1,8 @@ +{ + "translatorLanguages": [ + { "code": "fa-FA", "label": "persian" }, + { "code": "ar-AR", "label": "arabic" }, + { "code": "de-CH", "label": "swissGerman" }, + { "code": "en-US", "label": "english" } + ] +} diff --git a/src/locales/de-CH.json b/src/locales/de-CH.json index 599cf6f..b3f1636 100644 --- a/src/locales/de-CH.json +++ b/src/locales/de-CH.json @@ -3,12 +3,21 @@ "save": "Speichern", "delete": "Löschen", "edit": "Bearbeiten", + "back": "Zurück", + "next": "Weiter", + "reset": "Zurücksetzen", + "finish": "Beenden", "title": "Titel", "description": "Beschreibung", "administrative": "Administratives", "settings": "Einstellungen", "dashboard": "Dashboard", - "noResultsYetAvailable": "Keine Resultate bisher verfügbar" + "noResultsYetAvailable": "Keine Resultate bisher verfügbar", + "arabic": "Arabisch", + "english": "Englisch", + "persian": "Persisch/Farsi", + "swissGerman": "Schweizer Deutsch", + "required": "Wird benötigt..." }, "chapters": { "chapters": "Kapitel", @@ -24,5 +33,12 @@ "settings": { "title": "Einstellungen", "language": "Sprache" + }, + "setupWizard": { + "title": "Benutzer einrichten", + "chooseDisplayName": "Wähle deinen Benutzername", + "chooseLanguages": "Sprachen auswählen", + "chooseNotifications": "Benachrichtigungen", + "allStepsCompletedMessage": "Alle Schritte beendet - Fortfahren" } } diff --git a/src/locales/en-US.json b/src/locales/en-US.json index f8faa1f..8a34865 100644 --- a/src/locales/en-US.json +++ b/src/locales/en-US.json @@ -3,12 +3,23 @@ "save": "Save", "delete": "Delete", "edit": "Edit", + "back": "Back", + "next": "Next", + "reset": "Reset", + "finish": "Finish", "title": "Title", "description": "Description", "administrative": "Administrative", "settings": "Settings", "dashboard": "Dashboard", - "noResultsYetAvailable": "No results available yet" + "noResultsYetAvailable": "No results available yet", + "arabic": "Arabic", + "english": "English", + "persian": "Persian/Farsi", + "swissGerman": "Swiss German", + "required": "Required!", + "tooShort": "Too short!", + "tooLong": "Too long!" }, "chapters": { "chapters": "Chapters", @@ -23,6 +34,20 @@ }, "settings": { "title": "Settings", - "language": "Language" + "language": "Language", + "currentRole": "Current active role", + "admin": "Administrator", + "translator": "Translator", + "approver": "Approver", + "content-creator": "Content Creatror", + "viewer": "Viewer" + }, + "setupWizard": { + "title": "Setup your user account", + "chooseDisplayName": "Choose your user name", + "chooseLanguages": "Choose your language", + "chooseNotifications": "Notifications", + "receiveNotifications": "Receive notifications", + "allStepsCompletedMessage": "All steps completed - you're finished" } } diff --git a/src/pages/Settings/Settings.tsx b/src/pages/Settings/Settings.tsx index 750f510..264c991 100644 --- a/src/pages/Settings/Settings.tsx +++ b/src/pages/Settings/Settings.tsx @@ -1,47 +1,78 @@ import React from "react"; import { useTranslation } from "react-i18next"; +import * as Yup from "yup"; +import { useQuery, useMutation } from "react-apollo-hooks"; +import { Form, Formik, FormikActions } from "formik"; import { withStyles, WithStyles } from "@material-ui/core/styles"; import Typography from "@material-ui/core/Typography"; import Card from "@material-ui/core/Card"; import CardContent from "@material-ui/core/CardContent"; -import FormControl from "@material-ui/core/FormControl"; -import InputLabel from "@material-ui/core/InputLabel"; -import Select from "@material-ui/core/Select"; -import MenuItem from "@material-ui/core/MenuItem"; +import { Divider, CardActionArea, Button } from "@material-ui/core"; import { styles } from "src/styles"; +import GeneralSection from "src/pages/Settings/SettingsSection/GeneralSection"; +import LanguagesSection from "src/pages/Settings/SettingsSection/LanguagesSection"; +import NotificationSection from "src/pages/Settings/SettingsSection/NotificationSection"; +import i18next from "src/i18n"; +import { GET_SETTINGS, UPDATE_SETTINGS } from "src/queries/settings"; +import auth0Client from "src/auth/Auth"; + +export const UserSetupSchema = Yup.object().shape({ + userName: Yup.string() + .min(4, i18next.t("tooShort")) + .max(50, i18next.t("tooLong")) + .required(i18next.t("required")), + currentRole: Yup.string().required(i18next.t("required")) +}); interface Props extends WithStyles {} const Settings: React.FunctionComponent = ({ classes }) => { const { t, i18n } = useTranslation(); + const { data, error, loading } = useQuery(GET_SETTINGS); + const updateSettings = useMutation(UPDATE_SETTINGS); + + // Here we would now update the backend settings... + function handleSave(values: any, actions: FormikActions) { + const newSettings = { ...values }; + updateSettings({ variables: { settings: newSettings } }); + // TODO: This then has to go to the server... - const [values, setValues] = React.useState({ - title: "", - description: "" - }); + auth0Client.changeCurrentRole(newSettings.currentRole); + i18n.changeLanguage(newSettings.language); + localStorage.setItem("settings", JSON.stringify(newSettings)); + actions.setSubmitting(false); + } - // Note: Pulling the available languages is not so straightforward. So have to loop through the resources object: - // https://github.com/i18next/i18next/issues/1068 return ( {t("settings:title")} - - {t("settings:language")} - - + handleSave(values, actions)} + render={({ submitForm, values }) => ( +
+ + + + + + + + +
+ )} + />
diff --git a/src/pages/Settings/SettingsSection/GeneralSection.tsx b/src/pages/Settings/SettingsSection/GeneralSection.tsx new file mode 100644 index 0000000..4e48797 --- /dev/null +++ b/src/pages/Settings/SettingsSection/GeneralSection.tsx @@ -0,0 +1,72 @@ +import React, { useRef } from "react"; +import { useQuery, useMutation } from "react-apollo-hooks"; +import { useTranslation } from "react-i18next"; +import { Formik, Field, Form } from "formik"; +import { TextField } from "formik-material-ui"; + +import { withStyles, WithStyles, Grid, MenuItem } from "@material-ui/core"; + +import { styles } from "src/styles"; +import { GET_SETTINGS, UPDATE_SETTINGS } from "src/queries/settings"; +import auth0Client from "src/auth/Auth"; + +interface Props extends WithStyles { + settings?: any; + setSettings?: CallableFunction; + values?: any; +} + +function GeneralSection({ classes, values }: Props) { + const { t } = useTranslation(); + const { data, error, loading } = useQuery(GET_SETTINGS); + const updateSettings = useMutation(UPDATE_SETTINGS); + + const { settings } = data; + const roles = auth0Client.getAllowedRoles(); + + const handleChange = (name: string) => ( + event: + | React.ChangeEvent + | React.ChangeEvent + ) => { + if (name === "currentRole") { + auth0Client.changeCurrentRole(event.target.value); + } else { + updateSettings({ + variables: { ...settings, [name]: event.target.value } + }); + } + }; + + return ( + + + {roles.length ? ( + + {roles.map(r => ( + + {t(r)} + + ))} + + ) : null} + + ); +} + +export default withStyles(styles)(GeneralSection); diff --git a/src/pages/Settings/SettingsSection/LanguagesSection.tsx b/src/pages/Settings/SettingsSection/LanguagesSection.tsx new file mode 100644 index 0000000..a08c780 --- /dev/null +++ b/src/pages/Settings/SettingsSection/LanguagesSection.tsx @@ -0,0 +1,70 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; + +import { + withStyles, + WithStyles, + MenuItem, + Grid, + FormControlLabel, + Checkbox +} from "@material-ui/core"; + +import { styles } from "src/styles"; +import configurationJSON from "src/configuration.json"; +import { Field, FieldArray } from "formik"; +import { TextField, CheckboxWithLabel } from "formik-material-ui"; + +interface Props extends WithStyles { + values?: any; +} + +function LanguagesSection({ classes, values }: Props) { + const { t, i18n } = useTranslation(); + + return ( + + + {Object.keys(i18n.options.resources || {}).map((l: string) => ( + + {l} + + ))} + + + + configurationJSON.translatorLanguages.map(l => ( + { + if (e.target.checked) arrayHelpers.push(l.code); + else { + const idx = values.translatorLanguages.indexOf(l.code); + arrayHelpers.remove(idx); + } + }} + /> + } + /> + )) + } + /> + + ); +} + +export default withStyles(styles)(LanguagesSection); diff --git a/src/pages/Settings/SettingsSection/NotificationSection.tsx b/src/pages/Settings/SettingsSection/NotificationSection.tsx new file mode 100644 index 0000000..d24516b --- /dev/null +++ b/src/pages/Settings/SettingsSection/NotificationSection.tsx @@ -0,0 +1,56 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import { useQuery, useMutation } from "react-apollo-hooks"; + +import { + withStyles, + WithStyles, + FormControl, + Grid, + FormControlLabel, + FormLabel, + FormGroup, + Checkbox +} from "@material-ui/core"; + +import { styles } from "src/styles"; +import { GET_SETTINGS, UPDATE_SETTINGS } from "src/queries/settings"; + +interface Props extends WithStyles { + values: any; +} + +function GeneralSection({ classes }: Props) { + const { t, i18n } = useTranslation(); + const { data, error, loading } = useQuery(GET_SETTINGS); + const updateSettings = useMutation(UPDATE_SETTINGS); + + const handleCheckboxChange = (name: string) => ( + event: React.ChangeEvent + ) => { + updateSettings({ variables: { [name]: event.target.value } }); + }; + + return ( + + + {t("setupWizard:eventNotificationsTitle")} + + + } + label={t("setupWizard:receiveEventNotifications")} + /> + + + + ); +} + +export default withStyles(styles)(GeneralSection); diff --git a/src/pages/SetupWizard/SetupWizard.tsx b/src/pages/SetupWizard/SetupWizard.tsx new file mode 100644 index 0000000..753fdf8 --- /dev/null +++ b/src/pages/SetupWizard/SetupWizard.tsx @@ -0,0 +1,132 @@ +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; + +import Stepper from "@material-ui/core/Stepper"; +import Step from "@material-ui/core/Step"; +import StepLabel from "@material-ui/core/StepLabel"; +import Button from "@material-ui/core/Button"; +import Typography from "@material-ui/core/Typography"; +import { withStyles, WithStyles, Grid, Paper } from "@material-ui/core"; + +import { styles } from "src/styles"; +import GeneralSection from "src/pages/Settings/SettingsSection/GeneralSection"; +import LanguagesSection from "src/pages/Settings/SettingsSection/LanguagesSection"; +import NotificationSection from "src/pages/Settings/SettingsSection/NotificationSection"; +import { useQuery, useApolloClient, useMutation } from "react-apollo-hooks"; +import { GET_SETTINGS, UPDATE_SETTINGS } from "src/queries/settings"; +import { Formik, FormikActions, FormikBag, Form } from "formik"; +import { UserSetupSchema } from "src/pages/Settings/Settings"; + +function getSteps() { + return [ + "setupWizard:chooseGeneralSettings", + "setupWizard:chooseLanguages", + "setupWizard:chooseNotifications" + ]; +} + +function getStepContent(step: number) { + switch (step) { + case 0: + return GeneralSection; + case 1: + return LanguagesSection; + case 2: + return NotificationSection; + default: + return GeneralSection; + } +} + +interface Props extends WithStyles {} + +function SetupWizard({ classes }: Props) { + const { t } = useTranslation(); + const { data, error, loading } = useQuery(GET_SETTINGS); + const updateSettings = useMutation(UPDATE_SETTINGS); + + const [activeStep, setActiveStep] = useState(0); + + const steps = getSteps(); + + async function handleSubmit(values: any, actions: FormikActions) { + if (activeStep === steps.length - 1) { + const updatedSettings = { ...values, hasCompletedSetup: true }; + updateSettings({ variables: { settings: updatedSettings } }); + // TODO: This then has to go to the server... + localStorage.setItem("settings", JSON.stringify(updatedSettings)); + } else { + handleNext(); + actions.setSubmitting(false); + } + } + + function handleNext() { + if (activeStep === steps.length - 1) { + return null; + } + setActiveStep(prevActiveStep => prevActiveStep + 1); + } + + function handleBack() { + setActiveStep(prevActiveStep => prevActiveStep - 1); + } + + // Note here: We are getting a "dynamic" component + const ActiveStepContent: React.ElementType = getStepContent(activeStep); + + return ( + + + + {t("setupWizard:title")} + + handleSubmit(values, actions)} + render={({ submitForm, values }) => ( +
+ + + {steps.map(label => { + return ( + + {t(label)} + + ); + })} + + + + + + + + + +
+ )} + /> +
+
+ ); +} + +export default withStyles(styles)(SetupWizard); From 7013760da7e4d63fa8ecdbb5049e6e5543880901 Mon Sep 17 00:00:00 2001 From: David Friederich Date: Mon, 8 Apr 2019 22:49:09 +0200 Subject: [PATCH 005/180] use formik for newChapter mutations --- src/components/ErrorMessage.tsx | 19 ++++ src/locales/de-CH.json | 2 +- src/pages/Chapter/NewChapter.tsx | 173 ++++++++++++++++--------------- src/styles.ts | 5 +- 4 files changed, 113 insertions(+), 86 deletions(-) create mode 100644 src/components/ErrorMessage.tsx diff --git a/src/components/ErrorMessage.tsx b/src/components/ErrorMessage.tsx new file mode 100644 index 0000000..081ea56 --- /dev/null +++ b/src/components/ErrorMessage.tsx @@ -0,0 +1,19 @@ +import * as React from "react"; +import { useTranslation } from "react-i18next"; + +import { withStyles, WithStyles } from "@material-ui/core/styles"; + +import { styles } from "src/styles"; +import { Typography } from "@material-ui/core"; + +interface Props extends WithStyles { + error?: string; +} + +const ErrorMessage = ({ classes, error }: Props) => { + const { t } = useTranslation(); + + return {error}; +}; + +export default withStyles(styles, { withTheme: true })(ErrorMessage); diff --git a/src/locales/de-CH.json b/src/locales/de-CH.json index b3f1636..b2e944f 100644 --- a/src/locales/de-CH.json +++ b/src/locales/de-CH.json @@ -25,7 +25,7 @@ "noChaptersAvailable": "No keine Kapitel verfügbar", "newChapterTitle": "Neues Kapitel anlegen", "chapterNumber": "Kapitelnummer", - "chapterDescriptionPlaceholder": "Um was geht es in diesem Kapitel?" + "chapterDescription": "Um was geht es in diesem Kapitel?" }, "voggi": { "voggiList": "Voggi-Liste" diff --git a/src/pages/Chapter/NewChapter.tsx b/src/pages/Chapter/NewChapter.tsx index 651fc15..ffa15fb 100644 --- a/src/pages/Chapter/NewChapter.tsx +++ b/src/pages/Chapter/NewChapter.tsx @@ -1,110 +1,115 @@ import * as React from "react"; -import { Mutation } from "react-apollo"; -import { RouteComponentProps } from "react-router-dom"; import { useTranslation } from "react-i18next"; +import * as Yup from "yup"; +import { Formik, Form, Field, FormikActions } from "formik"; +import { TextField } from "formik-material-ui"; +import { useMutation } from "react-apollo-hooks"; import { withStyles, WithStyles } from "@material-ui/core/styles"; import Typography from "@material-ui/core/Typography"; -import TextField from "@material-ui/core/TextField"; import Card from "@material-ui/core/Card"; import CardContent from "@material-ui/core/CardContent"; import Button from "@material-ui/core/Button"; -import SaveIcon from "@material-ui/icons/Save"; import { styles } from "src/styles"; import { UPSERT_CHAPTER } from "src/queries/chapters"; -import { CircularProgress } from "@material-ui/core"; +import i18next from "src/i18n"; +import history from "src/history"; +import ErrorMessage from "src/components/ErrorMessage"; + +export const ChapterSchema = Yup.object().shape({ + title: Yup.string() + .min(4, i18next.t("tooShort")) + .max(50, i18next.t("tooLong")) + .required(i18next.t("required")), + number: Yup.number() + .min(1, i18next.t("tooLow")) + .max(20, i18next.t("tooHigh")) + .required(i18next.t("required")), + description: Yup.string().max(200, i18next.t("tooLong")) +}); interface Props extends WithStyles {} const NewChapter = ({ classes }: Props) => { const { t } = useTranslation(); - const [values, setValues] = React.useState({ - chapterNumber: "", - title: "", - description: "" - }); - const handleChange = (name: string) => ( - event: React.ChangeEvent - ) => { - setValues({ - ...values, - [name]: event.currentTarget.value - }); - }; + // TODO: Unfortunately, react-apollo-hooks doesn't support yet the error, loading object in mutations (unlike with query...) + const upsertChapter = useMutation(UPSERT_CHAPTER); + + async function handleSave(values: any, actions: FormikActions) { + // TODO: This verbose stuff won't be necessary anymore as soon useMutation also returns a error/loading object. + try { + await upsertChapter({ variables: { input: values } }); + history.push("/chapters"); + } catch (e) { + actions.setSubmitting(false); + actions.setStatus({ response: `${t("serverError")}: ${e.message}` }); + } + } + // Since useMutation currently doesn't return the second arguments such as loading, we can not use react-apollo-hooks for now... // https://github.com/trojanowski/react-apollo-hooks/issues/90 // const upsertChapter = useMutation; return ( - {t("chapters:newChapterTitle")} - + + {t("chapters:newChapterTitle")} -
- - - - - {(createChapter, { data, loading }) => ( - - - - )} - - + handleSave(values, actions)} + render={({ submitForm, values, isSubmitting, status }) => ( +
+ + + + + {status && status.response && ( + + )} + + + )} + />
diff --git a/src/styles.ts b/src/styles.ts index 91532f3..a9f2b9d 100644 --- a/src/styles.ts +++ b/src/styles.ts @@ -25,12 +25,15 @@ export const styles = (theme: Theme) => fullWidthContainer: { width: "100%" }, - card: { + card2: { display: "flex", flexDirection: "column", padding: theme.spacing.unit * 4, alignItems: "center" }, + card: { + padding: theme.spacing.unit * 4 + }, landingLogo: { display: "flex", width: theme.spacing.unit * 25, From c5ed6ff48e5594df22fb064bea9764a528a4b6ba Mon Sep 17 00:00:00 2001 From: David Friederich Date: Mon, 8 Apr 2019 22:51:35 +0200 Subject: [PATCH 006/180] removed unecessary comment --- src/pages/Chapter/NewChapter.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/pages/Chapter/NewChapter.tsx b/src/pages/Chapter/NewChapter.tsx index ffa15fb..0ada884 100644 --- a/src/pages/Chapter/NewChapter.tsx +++ b/src/pages/Chapter/NewChapter.tsx @@ -48,9 +48,6 @@ const NewChapter = ({ classes }: Props) => { } } - // Since useMutation currently doesn't return the second arguments such as loading, we can not use react-apollo-hooks for now... - // https://github.com/trojanowski/react-apollo-hooks/issues/90 - // const upsertChapter = useMutation; return ( From 307463f219813a80d5c06cf7464bbc1e5d244fb7 Mon Sep 17 00:00:00 2001 From: David Friederich Date: Mon, 8 Apr 2019 23:00:00 +0200 Subject: [PATCH 007/180] add changes from other branch regarding codegen --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c733eb3..d721ed2 100755 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Although the schema is publicly accesible (TODO: Should we want to hide it?), we - Make sure that the settings in `apollo.config.js` in the root directory are correct - Write a new GraphQL query, preferably in the "queries" directory - Then generate the types with: - `apollo client:codegen` + `apollo client:codegen --target typescript` - This should generate some files in a **generated** folder. You can then import these types and use them in your components... ### Note: From 1e38eebc6d8a7e85d311b103830f004c8e689c68 Mon Sep 17 00:00:00 2001 From: Yanick Schraner Date: Sat, 13 Apr 2019 12:40:28 +0200 Subject: [PATCH 008/180] Add initial files --- src/pages/Vocabulary/NewVocabulary.tsx | 0 src/pages/Vocabulary/Vocabularies.tsx | 0 src/pages/Vocabulary/Vocabulary.tsx | 0 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/pages/Vocabulary/NewVocabulary.tsx create mode 100644 src/pages/Vocabulary/Vocabularies.tsx create mode 100644 src/pages/Vocabulary/Vocabulary.tsx diff --git a/src/pages/Vocabulary/NewVocabulary.tsx b/src/pages/Vocabulary/NewVocabulary.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/pages/Vocabulary/Vocabularies.tsx b/src/pages/Vocabulary/Vocabularies.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/pages/Vocabulary/Vocabulary.tsx b/src/pages/Vocabulary/Vocabulary.tsx new file mode 100644 index 0000000..e69de29 From 4e76c8baea3c54481978c768aa9231b80e689c41 Mon Sep 17 00:00:00 2001 From: Yanick Schraner Date: Sun, 14 Apr 2019 09:43:02 +0200 Subject: [PATCH 009/180] Add initial code for wordgroups --- src/components/WordGroupCard.tsx | 49 +++++++++++ src/pages/Vocabulary/NewVocabulary.tsx | 0 src/pages/Vocabulary/Vocabularies.tsx | 0 src/pages/Vocabulary/Vocabulary.tsx | 0 src/pages/WordGroup/NewVocabulary.tsx | 116 +++++++++++++++++++++++++ src/pages/WordGroup/Vocabulary.tsx | 37 ++++++++ src/pages/WordGroup/WordGroups.tsx | 64 ++++++++++++++ src/queries/wordgroups.ts | 60 +++++++++++++ 8 files changed, 326 insertions(+) create mode 100644 src/components/WordGroupCard.tsx delete mode 100644 src/pages/Vocabulary/NewVocabulary.tsx delete mode 100644 src/pages/Vocabulary/Vocabularies.tsx delete mode 100644 src/pages/Vocabulary/Vocabulary.tsx create mode 100644 src/pages/WordGroup/NewVocabulary.tsx create mode 100644 src/pages/WordGroup/Vocabulary.tsx create mode 100644 src/pages/WordGroup/WordGroups.tsx create mode 100644 src/queries/wordgroups.ts diff --git a/src/components/WordGroupCard.tsx b/src/components/WordGroupCard.tsx new file mode 100644 index 0000000..1eb0e1e --- /dev/null +++ b/src/components/WordGroupCard.tsx @@ -0,0 +1,49 @@ +import * as React from "react"; +import { Link as RouterLink } from "react-router-dom"; +import { useTranslation } from "react-i18next"; + +import { withStyles, WithStyles } from "@material-ui/core/styles"; +import Typography from "@material-ui/core/Typography"; +import Card from "@material-ui/core/Paper"; +import CardContent from "@material-ui/core/CardContent"; +import CardActionArea from "@material-ui/core/CardActionArea"; +import AddIcon from "@material-ui/icons/Add"; +import Fab from "@material-ui/core/Fab"; + +import { styles } from "src/styles"; +import { chapters_chapters } from "src/queries/__generated__/chapters"; + +interface Props extends WithStyles { + wordgroup: chapters_chapters; // TODO: Should use the generated type! +} + +const WordGroupCard = ({ classes, wordgroup }: Props) => { + const { t } = useTranslation(); + + // Note: MUI links together with react-router-dom and Typescript are a bit tricky due to their dynamic nature + // See the discussion and provided solutions here... https://github.com/mui-org/material-ui/issues/7877 + // + + )} + /> + + + + ); +}; + +export default withStyles(styles, { withTheme: true })(NewChapter); diff --git a/src/pages/WordGroup/Vocabulary.tsx b/src/pages/WordGroup/Vocabulary.tsx new file mode 100644 index 0000000..158ce15 --- /dev/null +++ b/src/pages/WordGroup/Vocabulary.tsx @@ -0,0 +1,37 @@ +import * as React from "react"; + +import { withStyles, WithStyles } from "@material-ui/core/styles"; +import Typography from "@material-ui/core/Typography"; +import Card from "@material-ui/core/Card"; +import CardActions from "@material-ui/core/CardActions"; +import CardContent from "@material-ui/core/CardContent"; +import Button from "@material-ui/core/Button"; +import LoginIcon from "@material-ui/icons/AccountCircle"; +import Avatar from "@material-ui/core/Avatar"; +import { RouteComponentProps } from "react-router-dom"; + +import { styles } from "src/styles"; +import theme from "src/theme"; +import NewChapter from "./NewVocabulary"; + +// These can come from the router... See the route definitions +interface ChapterRouterProps { + id: string; +} + +interface Props + extends RouteComponentProps, + WithStyles {} + +const Chapter = ({ classes, match }: Props) => { + if (match.params.id === "new") { + return ; + } + return ( + + Kapitel {match.params.id} + + ); +}; + +export default withStyles(styles, { withTheme: true })(Chapter); diff --git a/src/pages/WordGroup/WordGroups.tsx b/src/pages/WordGroup/WordGroups.tsx new file mode 100644 index 0000000..0039b80 --- /dev/null +++ b/src/pages/WordGroup/WordGroups.tsx @@ -0,0 +1,64 @@ +import * as React from "react"; +import { Link as RouterLink } from "react-router-dom"; +import { useTranslation } from "react-i18next"; +import { useQuery } from "react-apollo-hooks"; + +import { withStyles, WithStyles } from "@material-ui/core/styles"; +import Typography from "@material-ui/core/Typography"; +import Grid from "@material-ui/core/Grid"; +import Card from "@material-ui/core/Paper"; +import CardContent from "@material-ui/core/CardContent"; +import CardActionArea from "@material-ui/core/CardActionArea"; +import AddIcon from "@material-ui/icons/Add"; +import Fab from "@material-ui/core/Fab"; + +import { styles } from "src/styles"; +import { GET_WORDGROUPS } from "src/queries/wordgroups"; +import ChapterCard from "src/components/ChapterCard"; +import BusyOrErrorCard from "src/components/BusyOrErrorCard"; +import { chapters_chapters } from "src/queries/__generated__/chapters"; + +interface Props extends WithStyles {} + +const WordGroups = ({ classes }: Props) => { + const { t } = useTranslation(); + + const { data, error, loading } = useQuery(GET_WORDGROUPS); + + // Note: MUI links together with react-router-dom and Typescript are a bit tricky due to their dynamic nature + // See the discussion and provided solutions here... https://github.com/mui-org/material-ui/issues/7877 + // - - )} - /> - - - - ); + {status && status.response && ( + + )} + + + )} + /> + + + + ); }; -export default withStyles(styles, { withTheme: true })(NewWordGroup); +export default withStyles(styles, {withTheme: true})(NewWordGroup); diff --git a/src/pages/WordGroup/WordGroup.tsx b/src/pages/WordGroup/WordGroup.tsx index bad6b10..d87485f 100644 --- a/src/pages/WordGroup/WordGroup.tsx +++ b/src/pages/WordGroup/WordGroup.tsx @@ -3,46 +3,76 @@ import * as React from "react"; import {withStyles, WithStyles} from "@material-ui/core/styles"; import {useQuery} from "react-apollo-hooks"; import {RouteComponentProps} from "react-router-dom"; +import AddIcon from "@material-ui/icons/Add"; import {styles} from "styles"; import NewWordGroup from "./NewWordGroup"; import {GET_WORDGROUP_BY_ID} from "queries/wordgroups"; import BusyOrErrorCard from "components/BusyOrErrorCard"; +import {wordGroup_wordGroup, wordGroup_wordGroup_words} from "../../queries/__generated__/wordGroup"; +import {convertGlobalToDbId} from "../../helpers"; +import {useTranslation} from "react-i18next"; +import Section from "../../components/Section"; +import SectionCardContainer from "../../components/SectionCardContainer"; +import {Grid} from "@material-ui/core"; +import WordCard from "../../components/WordCard"; +import auth0Client from "../../auth/Auth"; +import LinkCard from "../../components/LinkCard"; +import {wordGroupById_wordGroup} from "../../queries/__generated__/wordGroupById"; // These can come from the router... See the route definitions interface WordGroupRouterProps { - id: string; + id: string; } interface Props - extends RouteComponentProps, - WithStyles { + extends RouteComponentProps, + WithStyles { } const WordGroup = ({classes, match}: Props) => { + const {t} = useTranslation(); - const {data, error, loading} = useQuery(GET_WORDGROUP_BY_ID, { - variables: { - 'id': match.params.id - }, - skip: match.params.id === "new" - }); - - if (match.params.id === "new") { - return ; - } - - if (loading || error || !data.wordGroup) - return (); - - return ( - data && - data.wordGroup.edges && - ); + const {loading, data, error} = useQuery(GET_WORDGROUP_BY_ID, { + variables: { + id: convertGlobalToDbId(match.params.id) + }, + skip: match.params.id === "new" + }); + + if (match.params.id === "new") { + return ; + } + + let title_name = ` ${data && data.titleDe ? data.titleDe : ''} / ${data && data.titleCh ? data.titleCh : ''}`; + + return
+ + + {data && + data.words && + data.words.map((w: wordGroup_wordGroup_words | null) => ( + w ? + + + : null + ))} + {["admin"].includes(auth0Client.getCurrentRole() || "") ? ( + + } + helperText="wordGroups:createNewWordGroup" + /> + + ) : null} + +
}; export default withStyles(styles, {withTheme: true})(WordGroup); diff --git a/src/pages/WordGroup/WordGroups.tsx b/src/pages/WordGroup/WordGroups.tsx index a728e04..5866d87 100644 --- a/src/pages/WordGroup/WordGroups.tsx +++ b/src/pages/WordGroup/WordGroups.tsx @@ -1,72 +1,51 @@ import * as React from "react"; -import {Link as RouterLink} from "react-router-dom"; import {useTranslation} from "react-i18next"; import {useQuery} from "react-apollo-hooks"; import {withStyles, WithStyles} from "@material-ui/core/styles"; -import Typography from "@material-ui/core/Typography"; import Grid from "@material-ui/core/Grid"; -import AddIcon from "@material-ui/icons/Add"; -import Fab from "@material-ui/core/Fab"; import {styles} from "styles"; -import {GET_WORDGROUPS} from "queries/wordgroups"; -import WordGroupCard from "components/WordGroupCard"; import BusyOrErrorCard from "components/BusyOrErrorCard"; -import {wordGroups_wordGroups, wordGroups_wordGroups_edges} from "queries/__generated__/wordGroups"; import SectionCardContainer from "../../components/SectionCardContainer"; -import {chapters_chapters} from "../../queries/__generated__/chapters"; -import ChapterCard from "../../components/ChapterCard"; -import auth0Client from "../../auth/Auth"; -import LinkCard from "../../components/LinkCard"; import Section from "../../components/Section"; +import {GET_CHAPTER_WORDGROUPS} from "../../queries/chapters"; +import { + chapters_wordGroups, chapters_wordGroups_chapters_edges, +} from "../../queries/__generated__/chapters_wordGroups"; +import VoggiChapterCard from "../../components/VoggiChapterCard"; interface Props extends WithStyles { - wordgroup: wordGroups_wordGroups; + chapter_wordGroup: chapters_wordGroups; } const WordGroups = ({classes}: Props) => { const {t} = useTranslation(); - const {data, error, loading} = useQuery(GET_WORDGROUPS); + const {data, error, loading} = useQuery(GET_CHAPTER_WORDGROUPS); // Note: MUI links together with react-router-dom and Typescript are a bit tricky due to their dynamic nature // See the discussion and provided solutions here... https://github.com/mui-org/material-ui/issues/7877 // + + )} + /> + + + + ); +}; + +export default withStyles(styles, {withTheme: true})(NewWord); diff --git a/src/pages/WordGroup/NewWordGroup.tsx b/src/pages/WordGroup/NewWordGroup.tsx index 63f53c6..3759ce8 100644 --- a/src/pages/WordGroup/NewWordGroup.tsx +++ b/src/pages/WordGroup/NewWordGroup.tsx @@ -70,6 +70,7 @@ const NewWordGroup = ({classes}: Props) => { initialValues={{ titleDe: "", titleCh: "", + words: [] }} validationSchema={WordGroupSchema} onSubmit={(values, actions) => handleSave(values, actions)} diff --git a/src/pages/WordGroup/WordGroup.tsx b/src/pages/WordGroup/WordGroup.tsx index d87485f..390a573 100644 --- a/src/pages/WordGroup/WordGroup.tsx +++ b/src/pages/WordGroup/WordGroup.tsx @@ -18,7 +18,7 @@ import {Grid} from "@material-ui/core"; import WordCard from "../../components/WordCard"; import auth0Client from "../../auth/Auth"; import LinkCard from "../../components/LinkCard"; -import {wordGroupById_wordGroup} from "../../queries/__generated__/wordGroupById"; +import {wordGroupById, wordGroupById_wordGroup} from "../../queries/__generated__/wordGroupById"; // These can come from the router... See the route definitions interface WordGroupRouterProps { @@ -33,7 +33,7 @@ interface Props const WordGroup = ({classes, match}: Props) => { const {t} = useTranslation(); - const {loading, data, error} = useQuery(GET_WORDGROUP_BY_ID, { + const {loading, data, error} = useQuery(GET_WORDGROUP_BY_ID, { variables: { id: convertGlobalToDbId(match.params.id) }, @@ -44,7 +44,7 @@ const WordGroup = ({classes, match}: Props) => { return ; } - let title_name = ` ${data && data.titleDe ? data.titleDe : ''} / ${data && data.titleCh ? data.titleCh : ''}`; + let title_name = ` ${data && data.wordGroup ? data.wordGroup.titleDe : ''} / ${data && data.wordGroup ? data.wordGroup.titleCh : ''}`; return
@@ -52,25 +52,24 @@ const WordGroup = ({classes, match}: Props) => { {data && - data.words && - data.words.map((w: wordGroup_wordGroup_words | null) => ( + data.wordGroup && + data.wordGroup.words && + data.wordGroup.words.map((w: wordGroup_wordGroup_words | null) => ( w ? : null ))} - {["admin"].includes(auth0Client.getCurrentRole() || "") ? ( - - } - helperText="wordGroups:createNewWordGroup" - /> - - ) : null} + + } + helperText="wordGroups:addWordToWordGroup" + /> +
}; diff --git a/src/privateRoutes.ts b/src/privateRoutes.ts index 5c6cb6c..a3b6bc4 100644 --- a/src/privateRoutes.ts +++ b/src/privateRoutes.ts @@ -10,6 +10,7 @@ import Chapter from "./pages/Chapter/Chapter"; import WordGroups from "./pages/WordGroup/WordGroups" import WordGroup from "./pages/WordGroup/WordGroup" import ChapterWordGroups from "./pages/WordGroup/ChapterWordGroups"; +import NewWord from "./pages/WordGroup/NewWord"; /** * Roles defined as constants for reuse... @@ -87,6 +88,13 @@ export const mainRoutes: IPrivateRouteConfig[] = [ component: WordGroup, exact: true, path: "/wordgroups/:id" + }, + { + showInDrawer: false, + allowedRoles: allUsers, + component: NewWord, + exact: true, + path: "/wordgroups/:id}/add" } ]; From 8bbd3873123da96bfcd2f9cf6e287aed0eb2e9cc Mon Sep 17 00:00:00 2001 From: Yanick Schraner Date: Sun, 14 Jul 2019 10:53:11 +0200 Subject: [PATCH 019/180] Work on searchable multi select --- src/components/SearchableMultiSelect.tsx | 323 +++++++++++++++++++++++ 1 file changed, 323 insertions(+) diff --git a/src/components/SearchableMultiSelect.tsx b/src/components/SearchableMultiSelect.tsx index e69de29..880d81c 100644 --- a/src/components/SearchableMultiSelect.tsx +++ b/src/components/SearchableMultiSelect.tsx @@ -0,0 +1,323 @@ +import React, {CSSProperties, HTMLAttributes} from 'react'; +import clsx from 'clsx'; +import Select from 'react-select'; +import {createStyles, emphasize, makeStyles, useTheme, Theme} from '@material-ui/core/styles'; +import Typography from '@material-ui/core/Typography'; +import NoSsr from '@material-ui/core/NoSsr'; +import TextField, {BaseTextFieldProps} from '@material-ui/core/TextField'; +import Paper from '@material-ui/core/Paper'; +import Chip from '@material-ui/core/Chip'; +import MenuItem from '@material-ui/core/MenuItem'; +import CancelIcon from '@material-ui/icons/Cancel'; +import PropTypes from 'prop-types'; +import {ValueContainerProps} from 'react-select/lib/components/containers'; +import {ControlProps} from 'react-select/lib/components/Control'; +import {MenuProps, NoticeProps} from 'react-select/lib/components/Menu'; +import {MultiValueProps} from 'react-select/lib/components/MultiValue'; +import {OptionProps} from 'react-select/lib/components/Option'; +import {PlaceholderProps} from 'react-select/lib/components/Placeholder'; +import {SingleValueProps} from 'react-select/lib/components/SingleValue'; +import {ValueType} from 'react-select/lib/types'; + +interface OptionType { + label: string; + value: string; +} + +const suggestions: OptionType[] = [ + {label: 'Brunei Darussalam'}, +].map(suggestion => ({ + value: suggestion.label, + label: suggestion.label, +})); + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + root: { + flexGrow: 1, + height: 250, + }, + input: { + display: 'flex', + padding: 0, + height: 'auto', + }, + valueContainer: { + display: 'flex', + flexWrap: 'wrap', + flex: 1, + alignItems: 'center', + overflow: 'hidden', + }, + chip: { + margin: theme.spacing(0.5, 0.25), + }, + chipFocused: { + backgroundColor: emphasize( + theme.palette.type === 'light' ? theme.palette.grey[300] : theme.palette.grey[700], + 0.08, + ), + }, + noOptionsMessage: { + padding: theme.spacing(1, 2), + }, + singleValue: { + fontSize: 16, + }, + placeholder: { + position: 'absolute', + left: 2, + bottom: 6, + fontSize: 16, + }, + paper: { + position: 'absolute', + zIndex: 1, + marginTop: theme.spacing(1), + left: 0, + right: 0, + }, + divider: { + height: theme.spacing(2), + }, + }), +); + +function NoOptionsMessage(props: NoticeProps) { + return ( + + {props.children} + + ); +} + +NoOptionsMessage.propTypes = { + children: PropTypes.node, + innerProps: PropTypes.object, + selectProps: PropTypes.object.isRequired, +} as any; + +type InputComponentProps = Pick & HTMLAttributes; + +function inputComponent({inputRef, ...props}: InputComponentProps) { + return
; +} + +inputComponent.propTypes = { + inputRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), +} as any; + +function Control(props: ControlProps) { + const { + children, + innerProps, + innerRef, + selectProps: {classes, TextFieldProps}, + } = props; + + return ( + + ); +} + +Control.propTypes = { + children: PropTypes.node, + innerProps: PropTypes.object, + innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + selectProps: PropTypes.object.isRequired, +} as any; + +function Option(props: OptionProps) { + return ( + + {props.children} + + ); +} + +Option.propTypes = { + children: PropTypes.node, + innerProps: PropTypes.object, + innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + isFocused: PropTypes.bool, + isSelected: PropTypes.bool, +} as any; + +function Placeholder(props: PlaceholderProps) { + return ( + + {props.children} + + ); +} + +Placeholder.propTypes = { + children: PropTypes.node, + innerProps: PropTypes.object, + selectProps: PropTypes.object.isRequired, +} as any; + +function SingleValue(props: SingleValueProps) { + return ( + + {props.children} + + ); +} + +SingleValue.propTypes = { + children: PropTypes.node, + innerProps: PropTypes.object, + selectProps: PropTypes.object.isRequired, +} as any; + +function ValueContainer(props: ValueContainerProps) { + return
{props.children}
; +} + +ValueContainer.propTypes = { + children: PropTypes.node, + selectProps: PropTypes.object.isRequired, +} as any; + +function MultiValue(props: MultiValueProps) { + return ( + } + /> + ); +} + +MultiValue.propTypes = { + children: PropTypes.node, + isFocused: PropTypes.bool, + removeProps: PropTypes.object.isRequired, + selectProps: PropTypes.object.isRequired, +} as any; + +function Menu(props: MenuProps) { + return ( + + {props.children} + + ); +} + +Menu.propTypes = { + children: PropTypes.node, + innerProps: PropTypes.object, + selectProps: PropTypes.object, +} as any; + +const components = { + Control, + Menu, + MultiValue, + NoOptionsMessage, + Option, + Placeholder, + SingleValue, + ValueContainer, +}; + +export default function IntegrationReactSelect() { + const classes = useStyles(); + const theme = useTheme(); + const [single, setSingle] = React.useState>(null); + const [multi, setMulti] = React.useState>(null); + + function handleChangeSingle(value: ValueType) { + setSingle(value); + } + + function handleChangeMulti(value: ValueType) { + setMulti(value); + } + + const selectStyles = { + input: (base: CSSProperties) => ({ + ...base, + color: theme.palette.text.primary, + '& input': { + font: 'inherit', + }, + }), + }; + + return ( +
+ + + +
+ ); +} From 8174072b54ccf8b3ff7fe591b385ebea4a0a40a8 Mon Sep 17 00:00:00 2001 From: Yanick Schraner Date: Wed, 17 Jul 2019 09:26:23 +0200 Subject: [PATCH 020/180] Further work on add words --- package-lock.json | 51 +++++++++++++++++------ package.json | 7 ++-- src/locales/de-CH.json | 15 +++++++ src/locales/en-US.json | 15 +++++++ src/pages/WordGroup/NewWord.tsx | 62 ++++++++++++++++++++++------ src/pages/WordGroup/NewWordGroup.tsx | 17 +++++++- src/privateRoutes.ts | 2 +- 7 files changed, 138 insertions(+), 31 deletions(-) diff --git a/package-lock.json b/package-lock.json index ccef946..9636a09 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1743,7 +1743,6 @@ "version": "16.8.4", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.8.4.tgz", "integrity": "sha512-eIRpEW73DCzPIMaNBDP5pPIpK1KXyZwNgfxiVagb5iGiz6da+9A5hslSX6GAQKdO7SayVCS/Fr2kjqprgAvkfA==", - "dev": true, "requires": { "@types/react": "*" } @@ -1769,6 +1768,16 @@ "@types/react-router": "*" } }, + "@types/react-select": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/react-select/-/react-select-3.0.0.tgz", + "integrity": "sha512-eu+4qdByhx1Szqtpqke/7Bwhl5B1cWm47ojlYKp+PR2x9eVnzoytRoi2FNthQxCTQor4t81xgqY/8X4Naf2rKQ==", + "requires": { + "@types/react": "*", + "@types/react-dom": "*", + "@types/react-transition-group": "*" + } + }, "@types/react-swipeable-views": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@types/react-swipeable-views/-/react-swipeable-views-0.13.0.tgz", @@ -6128,9 +6137,9 @@ } }, "formik-material-ui": { - "version": "0.0.16", - "resolved": "https://registry.npmjs.org/formik-material-ui/-/formik-material-ui-0.0.16.tgz", - "integrity": "sha512-LVv7WG5o5VmKa2K1JUhr/NbCzgzlZrZXEUOXH57P44zVE+R8cOELy5U4RV7VU5lLQW4sq5nlBatW01WrqtfoDQ==" + "version": "0.0.19", + "resolved": "https://registry.npmjs.org/formik-material-ui/-/formik-material-ui-0.0.19.tgz", + "integrity": "sha512-1EgNO5jjMyEijEjzX187hk73odxOJ2Rlm6TBCmEg3hTEazrKO8Isfdx79ItyStMIvxdDI41p4UBgAOhyCSOsYg==" }, "forwarded": { "version": "0.1.2", @@ -7573,7 +7582,8 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true + "bundled": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -7610,7 +7620,8 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "concat-map": { "version": "0.0.1", @@ -7619,7 +7630,8 @@ }, "console-control-strings": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -7722,7 +7734,8 @@ }, "inherits": { "version": "2.0.3", - "bundled": true + "bundled": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -7732,6 +7745,7 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -7744,17 +7758,20 @@ "minimatch": { "version": "3.0.4", "bundled": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true + "bundled": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -7771,6 +7788,7 @@ "mkdirp": { "version": "0.5.1", "bundled": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -7843,7 +7861,8 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true + "bundled": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -7853,6 +7872,7 @@ "once": { "version": "1.4.0", "bundled": true, + "optional": true, "requires": { "wrappy": "1" } @@ -7928,7 +7948,8 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true + "bundled": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -7958,6 +7979,7 @@ "string-width": { "version": "1.0.2", "bundled": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -7975,6 +7997,7 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -8013,11 +8036,13 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true + "bundled": true, + "optional": true }, "yallist": { "version": "3.0.3", - "bundled": true + "bundled": true, + "optional": true } } } diff --git a/package.json b/package.json index 87887d1..99dfc2a 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "@sentry/browser": "^5.3.0", "@sentry/cli": "^1.44.1", "@types/react-beautiful-dnd": "^11.0.2", + "@types/react-select": "^3.0.0", "apollo-cache-inmemory": "^1.6.0", "apollo-client": "^2.6.0", "apollo-link": "^1.2.11", @@ -19,7 +20,7 @@ "date-fns": "^2.0.0-alpha.27", "dotenv": "^8.0.0", "formik": "^1.5.7", - "formik-material-ui": "0.0.16", + "formik-material-ui": "0.0.19", "graphql": "^14.3.1", "graphql-tag": "^2.10.1", "i18next": "^15.1.3", @@ -32,9 +33,9 @@ "react-i18next": "^10.10.0", "react-router-dom": "^5.0.0", "react-scripts": "^3.0.1", + "react-select": "latest", "react-swipeable-views": "^0.13.3", - "yup": "^0.27.0", - "react-select": "latest" + "yup": "^0.27.0" }, "scripts": { "start": "react-scripts start", diff --git a/src/locales/de-CH.json b/src/locales/de-CH.json index b8c40f5..a5b9831 100644 --- a/src/locales/de-CH.json +++ b/src/locales/de-CH.json @@ -56,6 +56,21 @@ "wordGroup:titleChHelper": "Der Titel dieser Wortgruppe auf Schweizerdeutsch.", "nWords": "# Wörter: " }, + "words": { + "createNewWord": "Neues Wort erstellen", + "textDE": "Deutsches Wort", + "textDEHelper": "Das Wort auf deutsch. (Bsp.: Einkaufen)", + "exampleSentenceDE": "Deutscher Beispielsatz", + "exampleSentenceDEHelper": "Bsp: Ich gehe heute Einkaufen.", + "audioDE": "Höraufnahme Deutsch", + "audioDEHelper": "Höraufnahme Wort und Beispielsatz", + "textCH": "Schweizerdeutsches Wort", + "textCHHelper": "Das Wort auf schweizerdeutsch. (Bsp. Poschte)", + "exampleSentenceCH": "Schweizerdeutscher Beispielsatz", + "exampleSentenceCHHelper": "Bsp: Iich gaa go Poschte.", + "audioCH": "Höraufnahme Schweizerdeutsch", + "audioCHHelper": "Höraufnahme Wort und Beispielsatz" + }, "settings": { "title": "Einstellungen", "language": "Sprache" diff --git a/src/locales/en-US.json b/src/locales/en-US.json index d49e504..9f2d8f6 100644 --- a/src/locales/en-US.json +++ b/src/locales/en-US.json @@ -61,6 +61,21 @@ "wordGroup:titleChHelper": "Title of the word group in Swiss German.", "nWords": "# of words: " }, + "words": { + "createNewWord": "Create a new word", + "textDE": "German word", + "textDEHelper": "The word in german (e.g. Einkaufen)", + "exampleSentenceDE": "German example sentence", + "exampleSentenceDEHelper": "E.g. Ich gehe einkaufen.", + "audioDE": "Audio file", + "audioDEHelper": "German audio file with spoken word and sentence", + "textCH": "Swiss German word", + "textCHHelper": "The word in swiss german (e.g. Poschte)", + "exampleSentenceCH": "Swiss German example sentence", + "exampleSentenceCHHelper": "E.g. Iich gaa go Poschte.", + "audioCH": "Audio file", + "audioCHHelper": "Swiss German audio file with spoken word and sentence" + }, "settings": { "title": "Settings", "language": "Language", diff --git a/src/pages/WordGroup/NewWord.tsx b/src/pages/WordGroup/NewWord.tsx index eb1edde..8765ad5 100644 --- a/src/pages/WordGroup/NewWord.tsx +++ b/src/pages/WordGroup/NewWord.tsx @@ -12,7 +12,6 @@ import CardContent from "@material-ui/core/CardContent"; import Button from "@material-ui/core/Button"; import {styles} from "styles"; -import {UPSERT_CHAPTER} from "queries/chapters"; import i18next from "i18n"; import history from "myHistory"; import ErrorMessage from "components/ErrorMessage"; @@ -23,7 +22,7 @@ import { UPDATE_DE_WORD, UPDATE_EN_WORD, UPDATE_FA_WORD } from "../../queries/wordgroups"; -import {wordGroup_wordGroup} from "../../queries/__generated__/wordGroup"; +import CustomSelect from "../../components/SearchableMultiSelect"; export const WordGroupSchema = Yup.object().shape({ titleCh: Yup.string() @@ -64,12 +63,16 @@ const NewWord = ({classes}: Props) => { return ( - {t("wordGroups:newWordGroupTitle")} + {t("words:createNewWord")} handleSave(values, actions)} @@ -77,23 +80,58 @@ const NewWord = ({classes}: Props) => {
+ + + + - {status && status.response && ( )} diff --git a/src/pages/WordGroup/NewWordGroup.tsx b/src/pages/WordGroup/NewWordGroup.tsx index 3759ce8..e27514a 100644 --- a/src/pages/WordGroup/NewWordGroup.tsx +++ b/src/pages/WordGroup/NewWordGroup.tsx @@ -12,7 +12,6 @@ import CardContent from "@material-ui/core/CardContent"; import Button from "@material-ui/core/Button"; import {styles} from "styles"; -import {UPSERT_CHAPTER} from "queries/chapters"; import i18next from "i18n"; import history from "myHistory"; import ErrorMessage from "components/ErrorMessage"; @@ -23,7 +22,7 @@ import { UPDATE_DE_WORD, UPDATE_EN_WORD, UPDATE_FA_WORD } from "../../queries/wordgroups"; -import {wordGroup_wordGroup} from "../../queries/__generated__/wordGroup"; +import CustomSelect from "../../components/SearchableMultiSelect"; export const WordGroupSchema = Yup.object().shape({ titleCh: Yup.string() @@ -36,6 +35,11 @@ export const WordGroupSchema = Yup.object().shape({ .required(i18next.t("required")), }); +const words = [ + { value: 'foo', label: 'Foo' }, + { value: 'bar', label: 'Bar' }, +]; + interface Props extends WithStyles { } @@ -95,6 +99,15 @@ const NewWordGroup = ({classes}: Props) => { fullWidth /> + + {status && status.response && ( )} diff --git a/src/privateRoutes.ts b/src/privateRoutes.ts index 057cb71..4d6fbe7 100644 --- a/src/privateRoutes.ts +++ b/src/privateRoutes.ts @@ -96,7 +96,7 @@ export const mainRoutes: IPrivateRouteConfig[] = [ allowedRoles: allUsers, component: NewWord, exact: true, - path: "/wordgroups/:id}/add" + path: "/wordgroups/:id/add" } ]; From 9795d2661ac73eb486386131aeff9f12c6cea045 Mon Sep 17 00:00:00 2001 From: David Friederich Date: Sun, 28 Jul 2019 08:15:29 +0200 Subject: [PATCH 021/180] Setup of "new" AuthContext --- package-lock.json | 42 ++++++++ package.json | 1 + src/contexts/AuthContext.tsx | 194 +++++++++++++++++++++++++++++++++++ src/index.tsx | 22 ++-- 4 files changed, 252 insertions(+), 7 deletions(-) create mode 100644 src/contexts/AuthContext.tsx diff --git a/package-lock.json b/package-lock.json index 1b528ed..07a911a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,18 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@auth0/auth0-spa-js": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@auth0/auth0-spa-js/-/auth0-spa-js-1.1.1.tgz", + "integrity": "sha512-cBVWk4f/DNH83f7yMaWAhozIQUXdLXOluqsRuqe0cbinpP009MoC/bjMAeBfWbH6tdX/vOToUwQOcXytuVQInA==", + "requires": { + "es-cookie": "^1.2.0", + "fast-text-encoding": "^1.0.0", + "qs": "^6.7.0", + "ts-polyfill": "^3.5.1", + "unfetch": "^4.1.0" + } + }, "@babel/code-frame": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", @@ -4964,6 +4976,11 @@ "object-keys": "^1.0.12" } }, + "es-cookie": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-cookie/-/es-cookie-1.2.0.tgz", + "integrity": "sha512-Rq23x7fV2NRhDbYHA+ebMUHJGyrHWtLd+xFoQ+Tq5kpsAv6FmqpC/SFdDpFJWiwNW7XUhG3GDxVKymDz7KxSRQ==" + }, "es-to-primitive": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", @@ -5757,6 +5774,11 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, + "fast-text-encoding": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.0.tgz", + "integrity": "sha512-R9bHCvweUxxwkDwhjav5vxpFvdPGlVngtqmx4pIZfSUhM/Q4NiIUHB456BAf+Q1Nwu3HEZYONtu+Rya+af4jiQ==" + }, "faye-websocket": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.1.tgz", @@ -12963,6 +12985,21 @@ "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.1.2.tgz", "integrity": "sha512-f5Knjh7XCyRIzoC/z1Su1yLLRrPrFCgtUAh/9fCSP6NKbATwpOL1+idQVXQokK9GRFURn/jYPGPfegIctwunoA==" }, + "ts-polyfill": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/ts-polyfill/-/ts-polyfill-3.5.1.tgz", + "integrity": "sha512-IrwtFfM7ryDo3bM7c23KRXVaTCKy7Lwfl1hXkuorpBnvSffSuoD474qBPX6dggH2/H2C10zaYYmZiMia2oDJUw==", + "requires": { + "core-js": "^3.1.3" + }, + "dependencies": { + "core-js": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.1.4.tgz", + "integrity": "sha512-YNZN8lt82XIMLnLirj9MhKDFZHalwzzrL9YLt6eb0T5D0EDl4IQ90IGkua8mHbnxNrkj1d8hbdizMc0Qmg1WnQ==" + } + } + }, "tslib": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", @@ -13087,6 +13124,11 @@ } } }, + "unfetch": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.1.0.tgz", + "integrity": "sha512-crP/n3eAPUJxZXM9T80/yv0YhkTEx2K1D3h7D1AJM6fzsWZrxdyRuLN0JH/dkZh1LNH8LxCnBzoPFCPbb2iGpg==" + }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", diff --git a/package.json b/package.json index 1f57ec2..ea42a0a 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "@auth0/auth0-spa-js": "^1.1.1", "@material-ui/core": "^4.0.1", "@material-ui/icons": "^4.0.1", "@material-ui/styles": "^4.0.1", diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx new file mode 100644 index 0000000..2a3ab87 --- /dev/null +++ b/src/contexts/AuthContext.tsx @@ -0,0 +1,194 @@ +import React, { useState, useEffect, useContext } from "react"; +import createAuth0Client from "@auth0/auth0-spa-js"; +import Auth0Client from "@auth0/auth0-spa-js/dist/typings/Auth0Client"; + +import { Role } from "rbac-rules"; + +const namespace = "https://admin.vochabular.ch/jwt/claims"; + +/** + * Properties from the Auth0 IdToken + the custom defined properties (in the Auth0 Rules!) + */ +interface IUser extends IdToken { + userId: string; + allowedOrganizations: string[]; + currentOrganization: string; + defaultRole: Role; + currentRole: Role; + allowedRoles: Role[]; +} + +interface IOwnIdToken extends IdToken { + [key: string]: any; +} + +/** + * Translates decoded Auth0-Id-token into a custom, flat user object + * @param idToken + */ +function getUserFromIdToken(idToken: IOwnIdToken): IUser { + // Strip the namespace, since we want to have a flat user object + const { [namespace]: customProperties, ...authProperties } = idToken; + return { + userId: customProperties["x-hasura-user-id"], + allowedOrganizations: customProperties["x-hasura-allowed-organizations"], + currentOrganization: customProperties["x-hasura-current-organization"], + defaultRole: customProperties["x-hasura-default-role"], + currentRole: customProperties["x-hasura-current-role"], + allowedRoles: customProperties["x-hasura-allowed-roles"], + ...authProperties + }; +} + +export interface IAuthContext { + isAuthenticated: boolean; + user: IUser | undefined; + idToken: string | undefined; + loading: boolean; + popupOpen: boolean; + loginWithPopup: CallableFunction; + handleRedirectCallback: CallableFunction; + getIdTokenClaims: CallableFunction; + /** + * TODO(df): How do we type this properly (a Callable...)? + */ + loginWithRedirect: CallableFunction; + getTokenSilently: CallableFunction; + getTokenWithPopup: CallableFunction; + logout: CallableFunction; +} + +const initialAuthContext: IAuthContext = { + isAuthenticated: false, + user: undefined, + idToken: undefined, + loading: true, + popupOpen: false, + loginWithPopup: () => console.info("Initializing..."), + handleRedirectCallback: () => console.info("Initializing..."), + getIdTokenClaims: () => console.info("Initializing..."), + loginWithRedirect: () => console.info("Initializing..."), + getTokenSilently: () => console.info("Initializing..."), + getTokenWithPopup: () => console.info("Initializing..."), + logout: () => console.info("Initializing...") +}; + +/** + * A function that routes the user to the right place after login + * @param appState + */ +export const onRedirectCallback = (appState: any) => { + window.history.replaceState( + {}, + document.title, + appState && appState.targetUrl + ? appState.targetUrl + : window.location.pathname + ); +}; + +const DEFAULT_REDIRECT_CALLBACK = () => + window.history.replaceState({}, document.title, window.location.pathname); + +export const AuthContext = React.createContext( + initialAuthContext +); +export const useAuth = () => useContext(AuthContext); + +// TODO(df): Improve typing... +export const AuthProvider = ({ + children, + onRedirectCallback = DEFAULT_REDIRECT_CALLBACK, + ...initOptions +}: any) => { + const [isAuthenticated, setIsAuthenticated] = useState(false); + const [user, setUser] = useState(); + const [auth0Client, setAuth0] = useState(); + const [idToken, setIdToken] = useState(); + const [loading, setLoading] = useState(true); + const [popupOpen, setPopupOpen] = useState(false); + + useEffect(() => { + let initAuth0 = async () => { + const auth0FromHook = await createAuth0Client(initOptions); + setAuth0(auth0FromHook); + + if (window.location.search.includes("code=")) { + const { appState } = await auth0FromHook.handleRedirectCallback(); + onRedirectCallback(appState); + } + const isAuthenticated = await auth0FromHook.isAuthenticated(); + + setIsAuthenticated(isAuthenticated); + + if (isAuthenticated) { + const user = await auth0FromHook.getUser(); + setUser(getUserFromIdToken(user)); + + // TODO(df): Hack to get the id-token, since Auth0 only can return the access-token, we however need the ID token... + const idToken = + // @ts-ignore + auth0FromHook.cache.cache["default::openid profile email"][ + "id_token" + ]; + setIdToken(idToken); + } + setLoading(false); + }; + initAuth0(); + // eslint-disable-next-line + }, []); + + const loginWithPopup = async (params = {}) => { + if (!auth0Client) return null; + setPopupOpen(true); + try { + await auth0Client.loginWithPopup(params); + } catch (error) { + console.error(error); + // TODO(df): Error handling! + } finally { + setPopupOpen(false); + } + const user = await auth0Client.getUser(); + setUser(getUserFromIdToken(user)); + setIsAuthenticated(true); + }; + + const handleRedirectCallback = async () => { + if (!auth0Client) return null; + setLoading(true); + await auth0Client.handleRedirectCallback(); + const user = await auth0Client.getUser(); + setLoading(false); + setIsAuthenticated(true); + setUser(getUserFromIdToken(user)); + }; + + return ( + + auth0Client && auth0Client.getIdTokenClaims(p), + loginWithRedirect: (p: RedirectLoginOptions) => + auth0Client && auth0Client.loginWithRedirect(p), + getTokenSilently: (p: GetTokenSilentlyOptions) => + auth0Client && auth0Client.getTokenSilently(p), + getTokenWithPopup: (p: GetTokenWithPopupOptions) => + auth0Client && auth0Client.getTokenWithPopup(p), + logout: (p: LogoutOptions) => + auth0Client && + auth0Client.logout(p || { returnTo: window.location.origin }) + }} + > + {children} + + ); +}; diff --git a/src/index.tsx b/src/index.tsx index e076acb..60bf208 100755 --- a/src/index.tsx +++ b/src/index.tsx @@ -13,6 +13,7 @@ import apolloClient from "./ApolloClient"; import theme from "./theme"; import * as serviceWorker from "./serviceWorker"; import packageJson from "../package.json"; +import { AuthProvider, onRedirectCallback } from "contexts/AuthContext"; // TODO: How can we use the values from our .env file? if (process.env.NODE_ENV === "production") { @@ -25,13 +26,20 @@ if (process.env.NODE_ENV === "production") { } ReactDOM.render( - - - - - - - , + + + + + + + + + , document.getElementById("root") ); From e700868e52f52314917206ad0dbad16287555616 Mon Sep 17 00:00:00 2001 From: David Friederich Date: Sun, 28 Jul 2019 08:16:15 +0200 Subject: [PATCH 022/180] Add necessary TODO for Apollo Client setup with new AuthContext --- src/ApolloClient.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ApolloClient.tsx b/src/ApolloClient.tsx index 84d8c1a..8ddd086 100644 --- a/src/ApolloClient.tsx +++ b/src/ApolloClient.tsx @@ -31,8 +31,14 @@ const cache = new InMemoryCache({}); // On each request, set the current idToken in the header const request = async (operation: any) => { + /** + * TODO(df): Need to get the token from the new AuthContext + * Something like: console.log(AuthContext["_currentValue"]); + */ operation.setContext({ headers: { + // Authorization: `Bearer ${idToken}`, + // "X-Hasura-Role": currentRole, authorization: "JWT " + auth0Client.getIdToken() } }); From 4d5615fac42b71fde675f80c76f5eba6c0951bf5 Mon Sep 17 00:00:00 2001 From: David Friederich Date: Sun, 28 Jul 2019 08:17:31 +0200 Subject: [PATCH 023/180] Adds a RBAC rules definition file. Contains all roles, permissions... --- src/rbac-rules.ts | 106 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 src/rbac-rules.ts diff --git a/src/rbac-rules.ts b/src/rbac-rules.ts new file mode 100644 index 0000000..031d527 --- /dev/null +++ b/src/rbac-rules.ts @@ -0,0 +1,106 @@ +/** + * Contains Role Based Access Control (RBAC) rules + * Insipired by: https://auth0.com/blog/role-based-access-control-rbac-and-react-apps/ + */ + +/** + * Role constants + * TODO(df): Should get the roles defined from Django via Auth0 Management API! + */ +export enum Role { + ADMINISTRATOR = "administrator", + APPROVER = "approver", + CONTENT_CREATOR = "creator-creator", + TRANSLATOR = "translator", + VIEWER = "viewer" +} + +/** + * Permission constants + * Note the "syntax" with the double __ as a seperator between resource and verb + */ +export enum Permission { + // Overall Pages + DASHBOARD_PAGE__VISIT = "dashboard:pageVisit", + CHAPTERS_PAGE__VISIT = "chapters:pageVisit", + WORD_GROUP__VISIT = "wordGroup:pageVisit", + // Views + CHAPTERS__QUERY = "chapters:query", + CHAPTER_CONTENT__QUERY = "chapterContent:query", + CHAPTER_CONTENT_COMMENTS__QUERY = "chapterContentComments:query", + WORD_GROUP__QUERY = "wordGroup:query", + // Actions + CHAPTER__CREATE = "chapter:create", + CHAPTER_CONTENT__CREATE = "chapterContent:create", + CHAPTER_CONTENT__EDIT = "chapterContent:edit", + CHAPTER_CONTENT_COMMENT__CREATE = "chapterContentComment:create", + CHAPTER_CONTENT_COMMENT__DELETE = "chapterContentComment:delete" +} + +export interface IRule { + static?: Permission[]; + dynamic?: any; // TODO(df): Type +} + +export type TRules = { [key in keyof typeof Role]: R }; + +const rules: TRules = { + // TODO(df): Or should we by default assume that the administrator can do everything? + ADMINISTRATOR: { + // Administrator has ALL permissions... + static: Object.values(Permission) + }, + APPROVER: { + static: [ + Permission.DASHBOARD_PAGE__VISIT, + Permission.CHAPTERS_PAGE__VISIT, + Permission.WORD_GROUP__VISIT, + Permission.CHAPTERS__QUERY, + Permission.CHAPTER_CONTENT__QUERY, + Permission.CHAPTER_CONTENT_COMMENTS__QUERY, + Permission.WORD_GROUP__QUERY, + Permission.CHAPTER_CONTENT_COMMENT__CREATE + ], + dynamic: { + CHAPTER_CONTENT_COMMENT__DELETE: ({ userId, commentUserId }: any) => { + if (!userId || !commentUserId) return false; + return userId === commentUserId; + } + } + }, + CONTENT_CREATOR: { + static: [ + Permission.DASHBOARD_PAGE__VISIT, + Permission.CHAPTERS_PAGE__VISIT, + Permission.WORD_GROUP__VISIT, + Permission.CHAPTERS__QUERY, + Permission.CHAPTER_CONTENT__QUERY, + Permission.CHAPTER_CONTENT__CREATE, + Permission.CHAPTER_CONTENT__EDIT, + Permission.CHAPTER_CONTENT_COMMENTS__QUERY, + Permission.WORD_GROUP__QUERY, + Permission.CHAPTER_CONTENT_COMMENT__CREATE + ], + dynamic: { + CHAPTER_CONTENT_COMMENT__DELETE: ({ userId, commentUserId }: any) => { + if (!userId || !commentUserId) return false; + return userId === commentUserId; + } + } + }, + TRANSLATOR: { + static: [ + Permission.DASHBOARD_PAGE__VISIT, + Permission.CHAPTERS_PAGE__VISIT, + Permission.WORD_GROUP__VISIT, + Permission.CHAPTERS__QUERY, + Permission.CHAPTER_CONTENT__QUERY, + Permission.WORD_GROUP__QUERY + ] + }, + VIEWER: { + static: [Permission.DASHBOARD_PAGE__VISIT] + } +}; + +export default rules; From 539568f54843da98de751b922b9147736b803a95 Mon Sep 17 00:00:00 2001 From: David Friederich Date: Sun, 28 Jul 2019 08:30:04 +0200 Subject: [PATCH 024/180] Adds the "Can" component --- src/components/Can/Can.tsx | 73 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 src/components/Can/Can.tsx diff --git a/src/components/Can/Can.tsx b/src/components/Can/Can.tsx new file mode 100644 index 0000000..792e79f --- /dev/null +++ b/src/components/Can/Can.tsx @@ -0,0 +1,73 @@ +import rules, { TRules, IRule, Role, Permission } from "rbac-rules"; +import { useAuth } from "contexts/AuthContext"; + +const check = ( + rules: TRules, + role: Role, + action: Permission, + data: any +) => { + const permissions = rules[role as any]; + if (!permissions) { + // role is not present in the rules + return false; + } + + const staticPermissions = permissions.static; + + if (staticPermissions && staticPermissions.includes(action)) { + // static rule not provided for action + return true; + } + + const dynamicPermissions = permissions.dynamic; + + if (dynamicPermissions) { + const permissionCondition = dynamicPermissions[action]; + if (!permissionCondition) { + // dynamic rule not provided for action + return false; + } + return permissionCondition(data); + } + return false; +}; + +interface CanProps { + /** + * A permission the component should check against. Extend the file for rbac-rules.ts as necessary. + */ + perform: Permission; + /** + * Set a role that you want to check the permission for. Usually not necessary, as by default, it will check the currentRole of the user + */ + role?: Role; + data?: any; + /*** + * Pass here a callable object that should be rendered depending on the result of the ACL check. + * Since most times you probably want to pass a component: () = + * TODO(df): Do some benchmarking, since using arrow function could have a negative performance impact here when using many CAN components in a list. + * Alternative: Pass a "Yes" and "No" Component that should get rendered accordingly + */ + yes: CallableFunction; + no: CallableFunction; +} + +/** + * A RBAC component that checks for a given permission, whether the currents user (default) role has access to this component. + */ +const Can = ({ role, perform, data, yes, no }: CanProps) => { + const { user } = useAuth(); + if (!role && !user) return null; + + return check( + rules, + role || (user && user.currentRole) || Role.VIEWER, + perform, + data + ) + ? yes() + : no(); +}; + +export default Can; From 8e23752b28d7fc12115a543eef5d7143cd6a7275 Mon Sep 17 00:00:00 2001 From: David Friederich Date: Sun, 28 Jul 2019 09:03:31 +0200 Subject: [PATCH 025/180] Necessary fixes due to role name change + adds Role enum everywhere possible --- src/PrivateApp.tsx | 3 ++- src/auth/Auth.ts | 9 +++++---- src/components/Drawer.tsx | 9 +++++---- src/pages/Settings/Settings.tsx | 6 +++++- src/privateRoutes.ts | 18 ++++++------------ 5 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/PrivateApp.tsx b/src/PrivateApp.tsx index 9b0c36f..9b69773 100644 --- a/src/PrivateApp.tsx +++ b/src/PrivateApp.tsx @@ -17,6 +17,7 @@ import auth0Client from "auth/Auth"; import { profile } from "queries/__generated__/profile"; import i18n from "i18n"; import LoadingPage from "pages/LoadingPage"; +import { Role } from "rbac-rules"; function isEmpty(obj: object) { return !obj || Object.keys(obj).length === 0; @@ -61,7 +62,7 @@ const PrivateApp: React.FunctionComponent = ({ classes }) => { // TODO: Need to actually get the current role from auth0Client. Via a setting to force a rerender? const accessibleRoutes = getAllAccessibleRoutes( - auth0Client.getCurrentRole() || "admin", + auth0Client.getCurrentRole() || Role.ADMINISTRATOR, false ); diff --git a/src/auth/Auth.ts b/src/auth/Auth.ts index 7f89a87..fb300f7 100644 --- a/src/auth/Auth.ts +++ b/src/auth/Auth.ts @@ -3,6 +3,7 @@ import auth0, { Auth0DecodedHash, Auth0UserProfile } from "auth0-js"; import client from "ApolloClient"; import { AUTH_CONFIG } from "./auth0-variables"; import history from "../myHistory"; +import { Role } from "rbac-rules"; /** * This class provides methods for authorization with OAuth provider "Auth0" @@ -14,8 +15,8 @@ class Auth { expiresAt: number; auth0: auth0.WebAuth; userProfile: Auth0UserProfile | undefined; - allowedRoles: string[]; - currentRole: string | undefined; + allowedRoles: Role[]; + currentRole: Role | undefined; dbId: number | undefined; constructor() { @@ -110,14 +111,14 @@ class Auth { /** * @returns Return the current set role of the current user... */ - public getCurrentRole = (): string | undefined => { + public getCurrentRole = (): Role | undefined => { return this.currentRole; }; /** * Changes the current role of a user */ - public changeCurrentRole = (newRole: string): void => { + public changeCurrentRole = (newRole: Role): void => { if (this.allowedRoles.includes(newRole)) { this.currentRole = newRole; /* diff --git a/src/components/Drawer.tsx b/src/components/Drawer.tsx index c4792b7..cbac08a 100644 --- a/src/components/Drawer.tsx +++ b/src/components/Drawer.tsx @@ -17,6 +17,7 @@ import ChevronLeftIcon from "@material-ui/icons/ChevronLeft"; import history from "myHistory"; import { styles } from "styles"; import { getAdministrativeRoutes, getMainRoutes } from "privateRoutes"; +import { Role } from "rbac-rules"; // TODO: We should have the AppBar in a own component. However, that messes up the layout... // import AppBar from "../components/AppBar"; @@ -40,10 +41,10 @@ const LinkListItem = (props: ILinkListItemProps) => ( ); function Drawer(props: Props) { - // TODO: Need to set the role based on the auth0 client! - const currentRole = "admin"; - const mainRoutes = getMainRoutes(currentRole || "", true); - const administrativeRoutes = getAdministrativeRoutes(currentRole || "", true); + // TODO(df): Need to set the role based on the auth0 client! + const currentRole = Role.ADMINISTRATOR; + const mainRoutes = getMainRoutes(currentRole, true); + const administrativeRoutes = getAdministrativeRoutes(currentRole, true); const { classes, isDrawerOpen, toggleDrawer } = props; // Get location to check which menu item should be active diff --git a/src/pages/Settings/Settings.tsx b/src/pages/Settings/Settings.tsx index a48789b..dc5912a 100644 --- a/src/pages/Settings/Settings.tsx +++ b/src/pages/Settings/Settings.tsx @@ -20,6 +20,7 @@ import auth0Client from "auth/Auth"; import BusyOrErrorCard from "components/BusyOrErrorCard"; import { profile_profile, profile } from "queries/__generated__/profile"; import { updateProfile } from "queries/__generated__/updateProfile"; +import { Role } from "rbac-rules"; export const UserSetupSchema = Yup.object().shape({ language: Yup.string().required(i18next.t("required")), @@ -53,7 +54,10 @@ const Settings: React.FunctionComponent = ({ classes }) => { ) { const newSettings = { ...values }; delete newSettings.id; - auth0Client.changeCurrentRole(newSettings.currentRole); + + // @ts-ignore + const currentRole: Role = Role[newSettings.currentRole]; + auth0Client.changeCurrentRole(currentRole); i18n.changeLanguage(newSettings.language); // Note: we need to strip the typename, as otherwise the backend complains and apollo client unfortunately doesn't strip it. TODO(df): need to find a central place to strip typenames generally... const { __typename, ...profile } = newSettings; diff --git a/src/privateRoutes.ts b/src/privateRoutes.ts index 7c809d6..ee237ee 100644 --- a/src/privateRoutes.ts +++ b/src/privateRoutes.ts @@ -10,15 +10,9 @@ import Chapter from "./pages/Chapter/Chapter"; import WordGroups from "./pages/WordGroup/WordGroups"; import WordGroup from "./pages/WordGroup/WordGroup"; import ChapterWordGroups from "./pages/WordGroup/ChapterWordGroups"; +import { Role } from "rbac-rules"; -/** - * Roles defined as constants for reuse... - */ -const ADMIN = "admin"; -const TRANSLATOR = "translator"; -const CONTENT_CREATOR = "content_creator"; -const VIEWER = "viewer"; -const allUsers = [ADMIN, TRANSLATOR, CONTENT_CREATOR, VIEWER]; +const allUsers = Object.values(Role); interface IPrivateRouteConfig { showInDrawer: boolean; @@ -26,7 +20,7 @@ interface IPrivateRouteConfig { exact?: boolean; icon?: any; // TODO: Need to find out how to use the proper type! path: string | string[]; - allowedRoles: string[]; + allowedRoles: Role[]; label?: string; } @@ -103,7 +97,7 @@ export const administrativeRoutes: IPrivateRouteConfig[] = [ } ]; -export function getMainRoutes(role: string, filterOnlyInDrawer: boolean) { +export function getMainRoutes(role: Role, filterOnlyInDrawer: boolean) { return mainRoutes.filter( e => e.allowedRoles.includes(role) && @@ -112,7 +106,7 @@ export function getMainRoutes(role: string, filterOnlyInDrawer: boolean) { } export function getAdministrativeRoutes( - role: string, + role: Role, filterOnlyInDrawer: boolean ) { return administrativeRoutes.filter( @@ -128,7 +122,7 @@ export function getAdministrativeRoutes( * @param filterOnlyInDrawer True if you want only the routes that should be in the drawer */ export function getAllAccessibleRoutes( - role: string, + role: Role, filterOnlyInDrawer: boolean ) { return [].concat( From ff5682e1a2efb72ee1b7cb637a0e1847fbf85712 Mon Sep 17 00:00:00 2001 From: David Friederich Date: Sun, 28 Jul 2019 09:39:50 +0200 Subject: [PATCH 026/180] adds Can component in both "NewChapter" locations --- src/pages/Chapter/Chapter.tsx | 21 ++++++++++++++++++--- src/pages/Chapter/Chapters.tsx | 31 +++++++++++++++++++------------ 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/src/pages/Chapter/Chapter.tsx b/src/pages/Chapter/Chapter.tsx index b6fc51b..d58a382 100644 --- a/src/pages/Chapter/Chapter.tsx +++ b/src/pages/Chapter/Chapter.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { RouteComponentProps } from "react-router-dom"; +import { RouteComponentProps, Redirect } from "react-router-dom"; import { useQuery } from "react-apollo-hooks"; import { useTranslation } from "react-i18next"; @@ -19,6 +19,8 @@ import Section from "components/Section"; import SectionCardContainer from "components/SectionCardContainer"; import { chapterById } from "queries/__generated__/chapterById"; import { convertGlobalToDbId } from "helpers"; +import { Permission } from "rbac-rules"; +import Can from "components/Can/Can"; // These can come from the router... See the route definitions interface ChapterRouterProps { @@ -51,7 +53,13 @@ const Chapter = ({ classes, match }: Props) => { // If its a new main chapter, don't need to query anything // TODO: This violates React Hook rules!!!! if (chapterId === "new") { - return ; + return ( + } + no={() => } + /> + ); } if (!data || !data.chapter || loading || error) @@ -65,7 +73,14 @@ const Chapter = ({ classes, match }: Props) => { // TODO: How do we handle new subchapters? if (subChapterId === "new") { - return ; + if (!data) return null; + return ( + } + no={() => } + /> + ); } // Render the subchapter screen diff --git a/src/pages/Chapter/Chapters.tsx b/src/pages/Chapter/Chapters.tsx index eda126f..8b1f128 100644 --- a/src/pages/Chapter/Chapters.tsx +++ b/src/pages/Chapter/Chapters.tsx @@ -10,10 +10,11 @@ import { GET_CHAPTERS } from "queries/chapters"; import ChapterCard from "components/ChapterCard"; import BusyOrErrorCard from "components/BusyOrErrorCard"; import LinkCard from "components/LinkCard"; -import auth0Client from "auth/Auth"; import Section from "components/Section"; import SectionCardContainer from "components/SectionCardContainer"; -import {chapters, chapters_chapters, chapters_chapters_edges} from "../../queries/__generated__/chapters"; +import { chapters } from "../../queries/__generated__/chapters"; +import Can from "components/Can/Can"; +import { Permission } from "rbac-rules"; interface Props extends WithStyles {} @@ -29,7 +30,9 @@ const Chapters = ({ classes }: Props) => { {data && data.chapters && @@ -39,15 +42,19 @@ const Chapters = ({ classes }: Props) => { ))} - {["admin"].includes(auth0Client.getCurrentRole() || "") ? ( - - } - helperText="createNewChapter" - /> - - ) : null} + ( + + } + helperText="createNewChapter" + /> + + )} + no={() => console.log("Nothing!!!")} + /> ); From c4b360ddf6970936f685a39c2a03c10702115430 Mon Sep 17 00:00:00 2001 From: Yanick Schraner Date: Sun, 28 Jul 2019 11:59:19 +0200 Subject: [PATCH 027/180] Further work on word management --- src/components/WordCard.tsx | 6 ++++-- src/pages/WordGroup/NewWord.tsx | 20 +++++++++++++------- src/pages/WordGroup/WordGroup.tsx | 2 +- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/components/WordCard.tsx b/src/components/WordCard.tsx index 4f2615c..a79f48e 100644 --- a/src/components/WordCard.tsx +++ b/src/components/WordCard.tsx @@ -10,12 +10,14 @@ import CardActionArea from "@material-ui/core/CardActionArea"; import {styles} from "styles"; import {wordGroups_wordGroups, wordGroups_wordGroups_edges_node} from "queries/__generated__/wordGroups"; import {wordGroup_wordGroup_words} from "../queries/__generated__/wordGroup"; +import {convertGlobalToDbId} from "../helpers"; interface Props extends WithStyles { word: wordGroup_wordGroup_words; + id: string; } -const WordCard = ({classes, word}: Props) => { +const WordCard = ({classes, word, id}: Props) => { // Note: MUI links together with react-router-dom and Typescript are a bit tricky due to their dynamic nature // See the discussion and provided solutions here... https://github.com/mui-org/material-ui/issues/7877 @@ -24,7 +26,7 @@ const WordCard = ({classes, word}: Props) => { { +interface WordGroupRouterProps { + id: string; } -const NewWord = ({classes}: Props) => { +interface Props + extends RouteComponentProps, + WithStyles { +} + +const NewWord = ({classes, match}: Props) => { const {t} = useTranslation(); // TODO: Unfortunately, react-apollo-hooks doesn't support yet the error, loading object in mutations (unlike with query...) - const insertWordGroup = useMutation(INSERT_WORDGROUP); + const createWord = useMutation(CREATE_WORD); const updateWordDe = useMutation(UPDATE_DE_WORD); const updateWordCh = useMutation(UPDATE_CH_WORD); const updateWordEn = useMutation(UPDATE_EN_WORD); @@ -52,8 +58,8 @@ const NewWord = ({classes}: Props) => { async function handleSave(values: any, actions: FormikActions) { // TODO: This verbose stuff won't be necessary anymore as soon useMutation also returns a error/loading object. try { - await insertWordGroup({variables: {input: values}}); - history.push('/wordgroups'); + await createWord({variables: {input: values}}); + history.push(`/wordgroups/${match.params.id}`); } catch (e) { actions.setSubmitting(false); actions.setStatus({response: `${t("serverError")}: ${e.message}`}); diff --git a/src/pages/WordGroup/WordGroup.tsx b/src/pages/WordGroup/WordGroup.tsx index 30cad99..3ad4018 100644 --- a/src/pages/WordGroup/WordGroup.tsx +++ b/src/pages/WordGroup/WordGroup.tsx @@ -59,7 +59,7 @@ const WordGroup = ({classes, match}: Props) => { data.wordGroup.words.map((w: wordGroup_wordGroup_words | null) => ( w ? - + : null ))} From 36b55100188eca122877f78727c1768277c243d9 Mon Sep 17 00:00:00 2001 From: David Friederich Date: Sun, 28 Jul 2019 17:33:19 +0200 Subject: [PATCH 028/180] updated namespace of token --- src/PrivateApp.tsx | 3 +++ src/auth/Auth.ts | 2 +- src/contexts/AuthContext.tsx | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/PrivateApp.tsx b/src/PrivateApp.tsx index 419fc2a..1bbd894 100644 --- a/src/PrivateApp.tsx +++ b/src/PrivateApp.tsx @@ -47,9 +47,12 @@ const PrivateApp: React.FunctionComponent = ({ classes }) => { * So we reload the page after a while, hoping that the user was created... */ if (!loading && data && isEmpty(data)) { + console.log(data, error); + /* setTimeout(function() { window.location.reload(); }, 1000); + */ return ; } diff --git a/src/auth/Auth.ts b/src/auth/Auth.ts index fb300f7..5a597ba 100644 --- a/src/auth/Auth.ts +++ b/src/auth/Auth.ts @@ -185,7 +185,7 @@ class Auth { */ private updateRoles = (idTokenPayload: any): void => { try { - const namespace = "https://admin.vochabular.ch/jwt/claims"; + const namespace = "https://hasura.io/jwt/claims"; const allowedRoles = idTokenPayload[namespace]["x-allowed-roles"]; const defaultRole = idTokenPayload[namespace]["x-default-role"]; this.allowedRoles = allowedRoles; diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx index c6f8683..3fb26df 100644 --- a/src/contexts/AuthContext.tsx +++ b/src/contexts/AuthContext.tsx @@ -4,7 +4,7 @@ import Auth0Client from "@auth0/auth0-spa-js/dist/typings/Auth0Client"; import { Role } from "rbac-rules"; -const namespace = "https://admin.vochabular.ch/jwt/claims"; +const namespace = "https://hasura.io/jwt/claims"; /** * Properties from the Auth0 IdToken + the custom defined properties (in the Auth0 Rules!) From c7f963cab05a0e172e0d4e6b372deeabb804bdd0 Mon Sep 17 00:00:00 2001 From: David Friederich Date: Sun, 28 Jul 2019 17:39:57 +0200 Subject: [PATCH 029/180] remove debug --- src/PrivateApp.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/PrivateApp.tsx b/src/PrivateApp.tsx index 1bbd894..d44ee7a 100644 --- a/src/PrivateApp.tsx +++ b/src/PrivateApp.tsx @@ -47,12 +47,10 @@ const PrivateApp: React.FunctionComponent = ({ classes }) => { * So we reload the page after a while, hoping that the user was created... */ if (!loading && data && isEmpty(data)) { - console.log(data, error); - /* setTimeout(function() { window.location.reload(); }, 1000); - */ + return ; } From 2fd74b861b63509163f855480c4d07cba187a680 Mon Sep 17 00:00:00 2001 From: David Friederich Date: Sun, 28 Jul 2019 20:54:40 +0200 Subject: [PATCH 030/180] Properly configure apollo codegen with same config values as .env file + adding Hasura admin secret to env file --- .env.example | 5 +++-- apollo.config.js | 15 ++++++--------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/.env.example b/.env.example index 999c6dc..b2f3cfe 100644 --- a/.env.example +++ b/.env.example @@ -2,5 +2,6 @@ NODE_PATH = "./" REACT_APP_AUTH0_DOMAIN = "------" REACT_APP_AUTH0_CLIENTID = "-----" REACT_APP_AUTH0_REDIRECT_URI = "http://localhost:3000/auth-callback" -REACT_APP_BACKEND_URL = "https://vochabular-admin.herokuapp.com/api" -REACT_APP_SENTRY_URL = "-----" \ No newline at end of file +REACT_APP_BACKEND_URL = "https://vochabular-admin-hasura.herokuapp.com/v1/graphql" +REACT_APP_SENTRY_URL = "--------" +REACT_APP_ADMIN_SECRET = "--------" \ No newline at end of file diff --git a/apollo.config.js b/apollo.config.js index 2c5d544..60e1658 100644 --- a/apollo.config.js +++ b/apollo.config.js @@ -1,16 +1,13 @@ +require("dotenv").config(); + module.exports = { client: { service: { - name: "vochabular-admin", - url: "http://vochabular-admin.herokuapp.com/api", - // optional headers - /* + name: "vochabular-admin-hasura", + url: process.env.REACT_APP_BACKEND_URL, headers: { - authorization: "Bearer lkjfalkfjadkfjeopknavadf" - }, - */ - // optional disable SSL validation check - skipSSLValidation: true + "x-hasura-admin-secret": process.env.REACT_APP_ADMIN_SECRET + } } } }; From 211660062b74b52b49fe7176808a87247c7b7067 Mon Sep 17 00:00:00 2001 From: David Friederich Date: Sun, 28 Jul 2019 20:59:11 +0200 Subject: [PATCH 031/180] fix Voggi query (tmp) --- src/pages/Dashboard/VoggiSection.tsx | 45 ++++++++++++++++------------ 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/src/pages/Dashboard/VoggiSection.tsx b/src/pages/Dashboard/VoggiSection.tsx index 03ccee8..746898c 100644 --- a/src/pages/Dashboard/VoggiSection.tsx +++ b/src/pages/Dashboard/VoggiSection.tsx @@ -1,43 +1,50 @@ import React from "react"; -import {useQuery} from "react-apollo-hooks"; +import { useQuery } from "react-apollo-hooks"; -import {withStyles, WithStyles} from "@material-ui/styles"; +import { withStyles, WithStyles } from "@material-ui/styles"; -import {styles} from "styles"; -import {GET_WORDGROUPS} from "queries/wordgroups"; +import { styles } from "styles"; +import { GET_WORDGROUPS } from "queries/wordgroups"; import BusyOrErrorCard from "components/BusyOrErrorCard"; -import {wordGroups_wordGroups, wordGroups_wordGroups_edges} from "queries/__generated__/wordGroups"; +import { + wordGroups_wordGroups, + wordGroups_wordGroups_edges +} from "queries/__generated__/wordGroups"; import Grid from "@material-ui/core/Grid"; import WordGroupCard from "components/WordGroupCard"; import SectionCardContainer from "../../components/SectionCardContainer"; import ChapterCard from "../../components/ChapterCard"; -import {wordGroup_wordGroup} from "../../queries/__generated__/wordGroup"; +import { wordGroup_wordGroup } from "../../queries/__generated__/wordGroup"; -interface Props extends WithStyles { -} +interface Props extends WithStyles {} -const VoggiSection: React.FunctionComponent = ({classes}) => { - const {data, error, loading} = useQuery(GET_WORDGROUPS); +const VoggiSection: React.FunctionComponent = ({ classes }) => { + const { data, error, loading } = useQuery(GET_WORDGROUPS); if (loading || error || !data.wordGroups.edges.length) return ( ); return ( {data && - data.wordGroups && - data.wordGroups.edges.map((w: wordGroups_wordGroups_edges) => ( - w && w.node ? ( - - - - ) : null - ))} + data.wordGroups && + data.wordGroups.edges.map((w: wordGroups_wordGroups_edges) => + w && w.node ? ( + + + + ) : null + )} ); }; From 911effa64c324e5d4478da1d2c542a286feaa9db Mon Sep 17 00:00:00 2001 From: David Friederich Date: Sun, 28 Jul 2019 21:27:54 +0200 Subject: [PATCH 032/180] query fixes necessary for apollo codegen --- src/queries/chapters.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/queries/chapters.ts b/src/queries/chapters.ts index 8bf0c34..82c3993 100644 --- a/src/queries/chapters.ts +++ b/src/queries/chapters.ts @@ -43,7 +43,6 @@ export const COMPONENT_PART = gql` edges { node { id - language textField } } @@ -153,13 +152,6 @@ export const UPSERT_CHAPTER = gql` createChapter(input: $input) { chapter { number - titleCH - titleDE - description - languages - fkBelongsTo { - id - } } } } From 155114975955973d0f8e47e0c52f10361f8f2705 Mon Sep 17 00:00:00 2001 From: David Friederich Date: Sun, 28 Jul 2019 21:28:15 +0200 Subject: [PATCH 033/180] additional query fixes for acg --- src/queries/languages.ts | 13 +-- src/queries/wordgroups.ts | 221 +++++++++++++++----------------------- 2 files changed, 96 insertions(+), 138 deletions(-) diff --git a/src/queries/languages.ts b/src/queries/languages.ts index 6ee65f8..96d388a 100644 --- a/src/queries/languages.ts +++ b/src/queries/languages.ts @@ -1,10 +1,11 @@ import gql from "graphql-tag"; export const GET_LANGUAGES = gql` -query languages { - languages{ - name - description + query getLanguages { + languages: api_language { + id + code + name + } } -} -`; \ No newline at end of file +`; diff --git a/src/queries/wordgroups.ts b/src/queries/wordgroups.ts index 3f73e35..17aacca 100644 --- a/src/queries/wordgroups.ts +++ b/src/queries/wordgroups.ts @@ -1,193 +1,150 @@ import gql from "graphql-tag"; export const GET_WORDGROUPS = gql` - query wordGroups { - wordGroups { - edges { - node { - id - titleCh - titleDe - fkChapter { - id - } - words { + query getWordGroups { + wordGroups: api_wordgroup { + id + titleCh: title_ch + titleDe: title_de + words { + word { id - wordch { - id - audio - text - exampleSentence - } - wordde { - id - audio - text - exampleSentence - } - worden { - id - audio - text - exampleSentence - } - wordar { - id - audio - text - exampleSentence - } - wordfa { + translations { id audio text - exampleSentence + exampleSentence: example_sentence + language { + id + code + } } } } } } -} `; export const GET_WORDGROUP_BY_ID = gql` - query wordGroupById($id: ID) { - wordGroup(id: $id) { - id - titleCh - titleDe - words { + query getWordGroupById($id: uuid!) { + wordGroup: api_wordgroup_by_pk(id: $id) { id - wordch { - id - exampleSentence - text - audio - } - wordde { - id - exampleSentence - text - audio - } - worden { - id - exampleSentence - text - audio - } - wordar { - id - exampleSentence - text - audio - } - wordfa { - id - exampleSentence - text - audio + titleCh: title_ch + titleDe: title_de + words { + word { + id + translations { + id + audio + text + exampleSentence: example_sentence + language { + id + code + } + } + } } } } -} `; export const INSERT_WORDGROUP = gql` mutation createWordGroup($input: IntroduceWordGroupInput!) { - createWordGroup(input: $input) { - wordGroup { - id - titleCh - titleDe - } + createWordGroup(input: $input) { + wordGroup { + id + titleCh + titleDe } } + } `; export const UPDATE_WORDGROUP = gql` mutation updateWordGroup($input: UpdateWordGroupInput!) { - updateWordGroup(input: $input) { - wordGroup { - id - titleCh - titleDe - } + updateWordGroup(input: $input) { + wordGroup { + id + titleCh + titleDe } } + } `; export const CREATE_WORD = gql` - mutation createWord($input: IntroduceWordInput!) { - createWord(input: $input) { - word { - id - } + mutation createWord($input: IntroduceWordInput!) { + createWord(input: $input) { + word { + id } } + } `; export const UPDATE_DE_WORD = gql` - mutation updateDEWord($input: UpdateWordDEInput!) { - updateDeWord(input: $input) { - word { - id - text - exampleSentence - audio - } + mutation updateDEWord($input: UpdateWordDEInput!) { + updateDeWord(input: $input) { + word { + id + text + exampleSentence + audio } } + } `; export const UPDATE_CH_WORD = gql` - mutation updateCHWord($input: UpdateWordCHInput!) { - updateChWord(input: $input) { - word { - id - text - exampleSentence - audio - } + mutation updateCHWord($input: UpdateWordCHInput!) { + updateChWord(input: $input) { + word { + id + text + exampleSentence + audio } } + } `; export const UPDATE_EN_WORD = gql` - mutation updateENWord($input: UpdateWordENInput!) { - updateEnWord(input: $input) { - word { - id - text - exampleSentence - audio - } + mutation updateENWord($input: UpdateWordENInput!) { + updateEnWord(input: $input) { + word { + id + text + exampleSentence + audio } } + } `; export const UPDATE_FA_WORD = gql` - mutation updateFAWord($input: UpdateWordFAInput!) { - updateFaWord(input: $input) { - word { - id - text - exampleSentence - audio - } + mutation updateFAWord($input: UpdateWordFAInput!) { + updateFaWord(input: $input) { + word { + id + text + exampleSentence + audio } } + } `; export const UPDATE_AR_WORD = gql` - mutation updateARWord($input: UpdateWordARInput!) { - updateArWord(input: $input) { - word { - id - text - exampleSentence - audio - } + mutation updateARWord($input: UpdateWordARInput!) { + updateArWord(input: $input) { + word { + id + text + exampleSentence + audio } } -`; \ No newline at end of file + } +`; From 8fac0709fa6ecd3e7e2939b401d5180bbd6522ff Mon Sep 17 00:00:00 2001 From: David Friederich Date: Sun, 28 Jul 2019 21:32:11 +0200 Subject: [PATCH 034/180] Ignore wordgroup queries for now for codegen --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4e89ef7..b3dff78 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", - "types": "apollo client:codegen --target typescript --globalTypesFile=src/__generated__/globalTypes.ts" + "types": "apollo client:codegen --target typescript --globalTypesFile=src/__generated__/globalTypes.ts --excludes=src/queries/wordgroups.ts" }, "eslintConfig": { "extends": "react-app" From 783f213f5301f4f1ee4135aab9ba552f33c7d2cd Mon Sep 17 00:00:00 2001 From: David Friederich Date: Sun, 28 Jul 2019 21:32:27 +0200 Subject: [PATCH 035/180] add generated types --- src/__generated__/globalTypes.ts | 82 +++---------------- src/queries/__generated__/CommentParts.ts | 2 +- src/queries/__generated__/ComponentParts.ts | 1 - src/queries/__generated__/UserParts.ts | 2 +- src/queries/__generated__/chapterById.ts | 1 - src/queries/__generated__/chapters.ts | 1 - .../chaptersWordGroupsByChapterId.ts | 2 +- src/queries/__generated__/createChapter.ts | 13 --- src/queries/__generated__/createComment.ts | 2 +- .../__generated__/getActiveComments.ts | 4 +- src/queries/__generated__/getAllComments.ts | 4 +- src/queries/__generated__/getLanguages.ts | 21 +++++ src/queries/__generated__/profile.ts | 2 +- src/queries/__generated__/updateProfile.ts | 2 +- 14 files changed, 43 insertions(+), 96 deletions(-) create mode 100644 src/queries/__generated__/getLanguages.ts diff --git a/src/__generated__/globalTypes.ts b/src/__generated__/globalTypes.ts index 9266158..e015477 100644 --- a/src/__generated__/globalTypes.ts +++ b/src/__generated__/globalTypes.ts @@ -36,21 +36,21 @@ export enum ProfileLanguage { } export interface ChapterInput { - titleCH: string; - titleDE: string; - fkBelongsToId?: string | null; description: string; - number: number; + fkBelongsToId?: string | null; languages: string; + number: number; + titleCH: string; + titleDE: string; } export interface CommentInput { - text: string; active: boolean; - fkAuthorId: string; context?: string | null; + fkAuthorId: string; fkComponentId: string; fkParentCommentId?: string | null; + text: string; } export interface IntroduceChapterInput { @@ -58,79 +58,21 @@ export interface IntroduceChapterInput { clientMutationId?: string | null; } -export interface IntroduceWordGroupInput { - wordGroupData?: WordGroupInput | null; - clientMutationId?: string | null; -} - -export interface IntroduceWordInput { - clientMutationId?: string | null; -} - export interface ProfileInput { + currentRole?: string | null; + eventNotifications?: boolean | null; firstname?: string | null; + language?: string | null; lastname?: string | null; roles?: string | null; - currentRole?: string | null; - language?: string | null; - translatorLanguages?: string | null; - eventNotifications?: boolean | null; setupCompleted?: boolean | null; -} - -export interface TranslatedWordInput { - text: string; - audio?: string | null; - exampleSentence?: string | null; + translatorLanguages?: string | null; } export interface UpdateProfileInput { - username?: string | null; - profileData?: ProfileInput | null; - clientMutationId?: string | null; -} - -export interface UpdateWordARInput { - wordId?: string | null; - wordData?: TranslatedWordInput | null; - clientMutationId?: string | null; -} - -export interface UpdateWordCHInput { - wordId?: string | null; - wordData?: TranslatedWordInput | null; - clientMutationId?: string | null; -} - -export interface UpdateWordDEInput { - wordId?: string | null; - wordData?: TranslatedWordInput | null; clientMutationId?: string | null; -} - -export interface UpdateWordENInput { - wordId?: string | null; - wordData?: TranslatedWordInput | null; - clientMutationId?: string | null; -} - -export interface UpdateWordFAInput { - wordId?: string | null; - wordData?: TranslatedWordInput | null; - clientMutationId?: string | null; -} - -export interface UpdateWordGroupInput { - wordGroupId?: string | null; - wordGroupData?: WordGroupInput | null; - clientMutationId?: string | null; -} - -export interface WordGroupInput { - fkChapterId: string; - titleDe: string; - titleCh: string; - words?: (string | null)[] | null; + profileData?: ProfileInput | null; + username?: string | null; } //============================================================== diff --git a/src/queries/__generated__/CommentParts.ts b/src/queries/__generated__/CommentParts.ts index 7db62ca..2bce5d9 100644 --- a/src/queries/__generated__/CommentParts.ts +++ b/src/queries/__generated__/CommentParts.ts @@ -10,7 +10,7 @@ import { CommentContext, ProfileLanguage } from "./../../__generated__/globalTyp export interface CommentParts_fkAuthor { __typename: "ProfileType"; - id: string; + id: any; firstname: string; lastname: string; roles: string; diff --git a/src/queries/__generated__/ComponentParts.ts b/src/queries/__generated__/ComponentParts.ts index b00a13b..357967b 100644 --- a/src/queries/__generated__/ComponentParts.ts +++ b/src/queries/__generated__/ComponentParts.ts @@ -14,7 +14,6 @@ export interface ComponentParts_texts_edges_node_translations_edges_node { * The ID of the object. */ id: string; - language: string; textField: string; } diff --git a/src/queries/__generated__/UserParts.ts b/src/queries/__generated__/UserParts.ts index d7922e3..f9ce4f6 100644 --- a/src/queries/__generated__/UserParts.ts +++ b/src/queries/__generated__/UserParts.ts @@ -10,7 +10,7 @@ import { ProfileLanguage } from "./../../__generated__/globalTypes"; export interface UserParts { __typename: "ProfileType"; - id: string; + id: any; firstname: string; lastname: string; roles: string; diff --git a/src/queries/__generated__/chapterById.ts b/src/queries/__generated__/chapterById.ts index fd29e5c..311c26b 100644 --- a/src/queries/__generated__/chapterById.ts +++ b/src/queries/__generated__/chapterById.ts @@ -119,7 +119,6 @@ export interface chapterById_chapter_components_edges_node_texts_edges_node_tran * The ID of the object. */ id: string; - language: string; textField: string; } diff --git a/src/queries/__generated__/chapters.ts b/src/queries/__generated__/chapters.ts index 1bb3756..494e9a0 100644 --- a/src/queries/__generated__/chapters.ts +++ b/src/queries/__generated__/chapters.ts @@ -50,7 +50,6 @@ export interface chapters_chapters_edges_node_componentSet_edges_node_texts_edge * The ID of the object. */ id: string; - language: string; textField: string; } diff --git a/src/queries/__generated__/chaptersWordGroupsByChapterId.ts b/src/queries/__generated__/chaptersWordGroupsByChapterId.ts index 3efd399..ffdb92f 100644 --- a/src/queries/__generated__/chaptersWordGroupsByChapterId.ts +++ b/src/queries/__generated__/chaptersWordGroupsByChapterId.ts @@ -16,7 +16,7 @@ export interface chaptersWordGroupsByChapterId_chapter_parentChapter { export interface chaptersWordGroupsByChapterId_chapter_wordGroups_edges_node_words { __typename: "WordType"; - id: string; + id: any; } export interface chaptersWordGroupsByChapterId_chapter_wordGroups_edges_node { diff --git a/src/queries/__generated__/createChapter.ts b/src/queries/__generated__/createChapter.ts index 8f59e47..e6bf4ef 100644 --- a/src/queries/__generated__/createChapter.ts +++ b/src/queries/__generated__/createChapter.ts @@ -8,22 +8,9 @@ import { IntroduceChapterInput } from "./../../__generated__/globalTypes"; // GraphQL mutation operation: createChapter // ==================================================== -export interface createChapter_createChapter_chapter_fkBelongsTo { - __typename: "ChapterType"; - /** - * The ID of the object. - */ - id: string; -} - export interface createChapter_createChapter_chapter { __typename: "ChapterType"; number: number; - titleCH: string; - titleDE: string; - description: string; - languages: string; - fkBelongsTo: createChapter_createChapter_chapter_fkBelongsTo | null; } export interface createChapter_createChapter { diff --git a/src/queries/__generated__/createComment.ts b/src/queries/__generated__/createComment.ts index e66ae98..fa0cffe 100644 --- a/src/queries/__generated__/createComment.ts +++ b/src/queries/__generated__/createComment.ts @@ -10,7 +10,7 @@ import { CommentInput, CommentContext, ProfileLanguage } from "./../../__generat export interface createComment_createComment_comment_fkAuthor { __typename: "ProfileType"; - id: string; + id: any; firstname: string; lastname: string; roles: string; diff --git a/src/queries/__generated__/getActiveComments.ts b/src/queries/__generated__/getActiveComments.ts index d0ef6e8..dc2c8c4 100644 --- a/src/queries/__generated__/getActiveComments.ts +++ b/src/queries/__generated__/getActiveComments.ts @@ -10,7 +10,7 @@ import { CommentContext, ProfileLanguage } from "./../../__generated__/globalTyp export interface getActiveComments_comments_edges_node_fkAuthor { __typename: "ProfileType"; - id: string; + id: any; firstname: string; lastname: string; roles: string; @@ -22,7 +22,7 @@ export interface getActiveComments_comments_edges_node_fkAuthor { export interface getActiveComments_comments_edges_node_commentSet_edges_node_fkAuthor { __typename: "ProfileType"; - id: string; + id: any; firstname: string; lastname: string; roles: string; diff --git a/src/queries/__generated__/getAllComments.ts b/src/queries/__generated__/getAllComments.ts index c6d2e93..cf3ed59 100644 --- a/src/queries/__generated__/getAllComments.ts +++ b/src/queries/__generated__/getAllComments.ts @@ -10,7 +10,7 @@ import { CommentContext, ProfileLanguage } from "./../../__generated__/globalTyp export interface getAllComments_comments_edges_node_fkAuthor { __typename: "ProfileType"; - id: string; + id: any; firstname: string; lastname: string; roles: string; @@ -22,7 +22,7 @@ export interface getAllComments_comments_edges_node_fkAuthor { export interface getAllComments_comments_edges_node_commentSet_edges_node_fkAuthor { __typename: "ProfileType"; - id: string; + id: any; firstname: string; lastname: string; roles: string; diff --git a/src/queries/__generated__/getLanguages.ts b/src/queries/__generated__/getLanguages.ts new file mode 100644 index 0000000..8032fd6 --- /dev/null +++ b/src/queries/__generated__/getLanguages.ts @@ -0,0 +1,21 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +// ==================================================== +// GraphQL query operation: getLanguages +// ==================================================== + +export interface getLanguages_languages { + __typename: "api_language"; + id: any; + code: string; + name: string; +} + +export interface getLanguages { + /** + * fetch data from the table: "api_language" + */ + languages: getLanguages_languages[]; +} diff --git a/src/queries/__generated__/profile.ts b/src/queries/__generated__/profile.ts index 53cbee9..2c99cbc 100644 --- a/src/queries/__generated__/profile.ts +++ b/src/queries/__generated__/profile.ts @@ -10,7 +10,7 @@ import { ProfileLanguage } from "./../../__generated__/globalTypes"; export interface profile_profile { __typename: "ProfileType"; - id: string; + id: any; firstname: string; lastname: string; roles: string; diff --git a/src/queries/__generated__/updateProfile.ts b/src/queries/__generated__/updateProfile.ts index 5f8bc01..0c7995b 100644 --- a/src/queries/__generated__/updateProfile.ts +++ b/src/queries/__generated__/updateProfile.ts @@ -10,7 +10,7 @@ import { UpdateProfileInput, ProfileLanguage } from "./../../__generated__/globa export interface updateProfile_updateProfile_profile { __typename: "ProfileType"; - id: string; + id: any; firstname: string; lastname: string; roles: string; From 2bf4dfe339dc9255aea23e493295beab7a709487 Mon Sep 17 00:00:00 2001 From: Yanick Schraner Date: Sun, 28 Jul 2019 21:32:42 +0200 Subject: [PATCH 036/180] Update queries to new hasura endpoint --- package-lock.json | 30 +--- src/components/WordCard.tsx | 2 +- src/pages/WordGroup/WordEditor.tsx | 23 +-- src/pages/WordGroup/WordGroup.tsx | 4 +- src/pages/WordGroup/WordGroupEditor.tsx | 10 +- src/queries/wordgroups.ts | 216 ++++++------------------ src/rbac-rules.ts | 2 +- 7 files changed, 74 insertions(+), 213 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7beaa4d..4fd0c09 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7604,8 +7604,7 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true, - "optional": true + "bundled": true }, "aproba": { "version": "1.2.0", @@ -7642,8 +7641,7 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "concat-map": { "version": "0.0.1", @@ -7652,8 +7650,7 @@ }, "console-control-strings": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "core-util-is": { "version": "1.0.2", @@ -7756,8 +7753,7 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, - "optional": true + "bundled": true }, "ini": { "version": "1.3.5", @@ -7767,7 +7763,6 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -7793,7 +7788,6 @@ "minipass": { "version": "2.3.5", "bundled": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -7810,7 +7804,6 @@ "mkdirp": { "version": "0.5.1", "bundled": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -7883,8 +7876,7 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, - "optional": true + "bundled": true }, "object-assign": { "version": "4.1.1", @@ -7894,7 +7886,6 @@ "once": { "version": "1.4.0", "bundled": true, - "optional": true, "requires": { "wrappy": "1" } @@ -7970,8 +7961,7 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, - "optional": true + "bundled": true }, "safer-buffer": { "version": "2.1.2", @@ -8001,7 +7991,6 @@ "string-width": { "version": "1.0.2", "bundled": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -8019,7 +8008,6 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -8058,13 +8046,11 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, - "optional": true + "bundled": true }, "yallist": { "version": "3.0.3", - "bundled": true, - "optional": true + "bundled": true } } } diff --git a/src/components/WordCard.tsx b/src/components/WordCard.tsx index a79f48e..f2661fb 100644 --- a/src/components/WordCard.tsx +++ b/src/components/WordCard.tsx @@ -26,7 +26,7 @@ const WordCard = ({classes, word, id}: Props) => { , WithStyles { + values: any } -const UpsertWord = ({classes, match}: Props) => { +const defaultValues = {text: '', audio: '', example_sentence: ''}; + +const WordEditor = ({classes, match, values = defaultValues}: Props) => { const {t} = useTranslation(); // TODO: Unfortunately, react-apollo-hooks doesn't support yet the error, loading object in mutations (unlike with query...) - const createWord = useMutation(CREATE_WORD); - const updateWordDe = useMutation(UPDATE_DE_WORD); - const updateWordCh = useMutation(UPDATE_CH_WORD); - const updateWordEn = useMutation(UPDATE_EN_WORD); - const updateWordAr = useMutation(UPDATE_AR_WORD); - const updateWordFa = useMutation(UPDATE_FA_WORD); + const upsertWord = useMutation(UPSERT_WORD); + async function handleSave(values: any, actions: FormikActions) { // TODO: This verbose stuff won't be necessary anymore as soon useMutation also returns a error/loading object. try { - await createWord({variables: {input: values}}); + await upsertWord({variables: {input: values}}); history.push(`/wordgroups/${match.params.id}`); } catch (e) { actions.setSubmitting(false); @@ -158,4 +153,4 @@ const UpsertWord = ({classes, match}: Props) => { ); }; -export default withStyles(styles, {withTheme: true})(UpsertWord); +export default withStyles(styles, {withTheme: true})(WordEditor); diff --git a/src/pages/WordGroup/WordGroup.tsx b/src/pages/WordGroup/WordGroup.tsx index 3ad4018..dd03bde 100644 --- a/src/pages/WordGroup/WordGroup.tsx +++ b/src/pages/WordGroup/WordGroup.tsx @@ -6,7 +6,7 @@ import {RouteComponentProps} from "react-router-dom"; import AddIcon from "@material-ui/icons/Add"; import {styles} from "styles"; -import NewWordGroup from "./NewWordGroup"; +import NewWordGroup from "./WordGroupEditor"; import {GET_WORDGROUP_BY_ID} from "queries/wordgroups"; import BusyOrErrorCard from "components/BusyOrErrorCard"; import { wordGroup_wordGroup_words} from "../../queries/__generated__/wordGroup"; @@ -64,7 +64,7 @@ const WordGroup = ({classes, match}: Props) => { ))} } helperText="wordGroups:addWordToWordGroup" /> diff --git a/src/pages/WordGroup/WordGroupEditor.tsx b/src/pages/WordGroup/WordGroupEditor.tsx index 966328c..bf885f3 100644 --- a/src/pages/WordGroup/WordGroupEditor.tsx +++ b/src/pages/WordGroup/WordGroupEditor.tsx @@ -16,7 +16,7 @@ import i18next from "i18n"; import history from "myHistory"; import ErrorMessage from "components/ErrorMessage"; import { - INSERT_WORDGROUP + UPSERT_WORDGROUP } from "../../queries/wordgroups"; import CustomSelect from "../../components/SearchableMultiSelect"; @@ -39,16 +39,16 @@ const words = [ interface Props extends WithStyles { } -const NewWordGroup = ({classes}: Props) => { +const WordGroupEditor = ({classes}: Props) => { const {t} = useTranslation(); // TODO: Unfortunately, react-apollo-hooks doesn't support yet the error, loading object in mutations (unlike with query...) - const insertWordGroup = useMutation(INSERT_WORDGROUP); + const upsertWordGroup = useMutation(UPSERT_WORDGROUP); async function handleSave(values: any, actions: FormikActions) { // TODO: This verbose stuff won't be necessary anymore as soon useMutation also returns a error/loading object. try { - await insertWordGroup({variables: {input: values}}); + await upsertWordGroup({variables: {input: values}}); history.push('/wordgroups'); } catch (e) { actions.setSubmitting(false); @@ -119,4 +119,4 @@ const NewWordGroup = ({classes}: Props) => { ); }; -export default withStyles(styles, {withTheme: true})(NewWordGroup); +export default withStyles(styles, {withTheme: true})(WordGroupEditor); diff --git a/src/queries/wordgroups.ts b/src/queries/wordgroups.ts index 3f73e35..dbec7df 100644 --- a/src/queries/wordgroups.ts +++ b/src/queries/wordgroups.ts @@ -1,193 +1,73 @@ import gql from "graphql-tag"; -export const GET_WORDGROUPS = gql` - query wordGroups { - wordGroups { - edges { - node { - id - titleCh - titleDe - fkChapter { - id - } - words { - id - wordch { - id - audio - text - exampleSentence - } - wordde { - id - audio - text - exampleSentence - } - worden { - id - audio - text - exampleSentence - } - wordar { - id - audio - text - exampleSentence - } - wordfa { - id - audio - text - exampleSentence - } - } - } - } - } -} -`; - -export const GET_WORDGROUP_BY_ID = gql` - query wordGroupById($id: ID) { - wordGroup(id: $id) { +export const WORDGROUP_FRAGMENT = gql` +fragment WordgroupParts on api_wordgroup { + fk_chapter_id + id + title_ch + title_de + words { id - titleCh - titleDe - words { + word { id - wordch { - id - exampleSentence - text - audio - } - wordde { - id - exampleSentence - text - audio - } - worden { - id - exampleSentence - text + wordTranslations { audio - } - wordar { - id - exampleSentence + example_sentence + language { + code + name + } text - audio - } - wordfa { id - exampleSentence - text - audio } } } } `; -export const INSERT_WORDGROUP = gql` - mutation createWordGroup($input: IntroduceWordGroupInput!) { - createWordGroup(input: $input) { - wordGroup { - id - titleCh - titleDe - } - } - } -`; - -export const UPDATE_WORDGROUP = gql` - mutation updateWordGroup($input: UpdateWordGroupInput!) { - updateWordGroup(input: $input) { - wordGroup { - id - titleCh - titleDe - } - } - } -`; - -export const CREATE_WORD = gql` - mutation createWord($input: IntroduceWordInput!) { - createWord(input: $input) { - word { - id - } - } - } +export const GET_WORDGROUPS = gql` +subscription getWordgroup { + api_wordgroup { + ...WordgroupParts + } +} +${WORDGROUP_FRAGMENT} `; -export const UPDATE_DE_WORD = gql` - mutation updateDEWord($input: UpdateWordDEInput!) { - updateDeWord(input: $input) { - word { - id - text - exampleSentence - audio - } - } - } +export const GET_WORDGROUP_BY_ID = gql` +subscription wordgroupById($id: uuid) { + api_wordgroup(where: {id: {_eq: $id}}) { + ...WordgroupParts + } +} +${WORDGROUP_FRAGMENT} `; -export const UPDATE_CH_WORD = gql` - mutation updateCHWord($input: UpdateWordCHInput!) { - updateChWord(input: $input) { - word { - id - text - exampleSentence - audio - } - } +export const INSERT_WORDGROUP = gql` +mutation insertWordGroup($input: [api_wordgroup_insert_input!]!) { + insert_api_wordgroup(objects: $input) { + returning { + id + fk_chapter_id + title_ch + title_de } + } +} `; -export const UPDATE_EN_WORD = gql` - mutation updateENWord($input: UpdateWordENInput!) { - updateEnWord(input: $input) { - word { - id - text - exampleSentence - audio - } - } +export const UPDATE_WORDGROUP = gql` +mutation updateWordGroup($id: uuid, $input: api_wordgroup_set_input!) { + update_api_wordgroup(where: {id: {_eq: $id}}, _set: $input) { + returning { + id + title_ch + title_de } + } +} `; -export const UPDATE_FA_WORD = gql` - mutation updateFAWord($input: UpdateWordFAInput!) { - updateFaWord(input: $input) { - word { - id - text - exampleSentence - audio - } - } - } -`; +export const INSERT_WORD = gql` -export const UPDATE_AR_WORD = gql` - mutation updateARWord($input: UpdateWordARInput!) { - updateArWord(input: $input) { - word { - id - text - exampleSentence - audio - } - } - } `; \ No newline at end of file diff --git a/src/rbac-rules.ts b/src/rbac-rules.ts index 031d527..3df2411 100644 --- a/src/rbac-rules.ts +++ b/src/rbac-rules.ts @@ -10,7 +10,7 @@ export enum Role { ADMINISTRATOR = "administrator", APPROVER = "approver", - CONTENT_CREATOR = "creator-creator", + CONTENT_CREATOR = "content-creator", TRANSLATOR = "translator", VIEWER = "viewer" } From fe7626e0cd07d7e1f0852f25ef3949012a8911ef Mon Sep 17 00:00:00 2001 From: David Friederich Date: Sun, 28 Jul 2019 21:55:41 +0200 Subject: [PATCH 037/180] some aliasing in wordgroup queries --- src/queries/wordgroups.ts | 64 +++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/src/queries/wordgroups.ts b/src/queries/wordgroups.ts index 58614be..90a5f81 100644 --- a/src/queries/wordgroups.ts +++ b/src/queries/wordgroups.ts @@ -2,17 +2,17 @@ import gql from "graphql-tag"; export const WORDGROUP_FRAGMENT = gql` fragment WordgroupParts on api_wordgroup { - fk_chapter_id + parentChapterId: fk_chapter_id id - title_ch - title_de + titleCh: title_ch + titleDe: title_de words { id word { id - wordTranslations { + translations { audio - example_sentence + exampleSentence: example_sentence language { code name @@ -25,48 +25,46 @@ fragment WordgroupParts on api_wordgroup { `; export const GET_WORDGROUPS = gql` -subscription getWordgroup { - api_wordgroup { - ...WordgroupParts + subscription subscribeWordGroups { + wordGroups: api_wordgroup { + ...WordgroupParts + } } -} -${WORDGROUP_FRAGMENT} + ${WORDGROUP_FRAGMENT} `; export const GET_WORDGROUP_BY_ID = gql` -subscription wordgroupById($id: uuid) { - api_wordgroup(where: {id: {_eq: $id}}) { - ...WordgroupParts + subscription subscribeWordGroupById($id: uuid) { + wordGroup: api_wordgroup(where: { id: { _eq: $id } }) { + ...WordgroupParts + } } -} -${WORDGROUP_FRAGMENT} + ${WORDGROUP_FRAGMENT} `; export const INSERT_WORDGROUP = gql` -mutation insertWordGroup($input: [api_wordgroup_insert_input!]!) { - insert_api_wordgroup(objects: $input) { - returning { - id - fk_chapter_id - title_ch - title_de + mutation insertWordGroup($input: [api_wordgroup_insert_input!]!) { + insert_api_wordgroup(objects: $input) { + returning { + id + parentChapterId: fk_chapter_id + titleCh: title_ch + titleDe: title_de + } } } -} `; export const UPDATE_WORDGROUP = gql` -mutation updateWordGroup($id: uuid, $input: api_wordgroup_set_input!) { - update_api_wordgroup(where: {id: {_eq: $id}}, _set: $input) { - returning { - id - title_ch - title_de + mutation updateWordGroup($id: uuid, $input: api_wordgroup_set_input!) { + update_api_wordgroup(where: { id: { _eq: $id } }, _set: $input) { + returning { + id + titleCh: title_ch + titleDe: title_de + } } } -} `; -export const INSERT_WORD = gql` - -`; +export const INSERT_WORD = gql``; From b59fb750b051d12688f74555400be941f8a2e267 Mon Sep 17 00:00:00 2001 From: David Friederich Date: Mon, 29 Jul 2019 14:18:06 +0200 Subject: [PATCH 038/180] reverting apollo codegen exclusion --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b3dff78..4e89ef7 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", - "types": "apollo client:codegen --target typescript --globalTypesFile=src/__generated__/globalTypes.ts --excludes=src/queries/wordgroups.ts" + "types": "apollo client:codegen --target typescript --globalTypesFile=src/__generated__/globalTypes.ts" }, "eslintConfig": { "extends": "react-app" From 30b95d1f24303a437190de33e5b28ac217149316 Mon Sep 17 00:00:00 2001 From: David Friederich Date: Mon, 29 Jul 2019 14:18:44 +0200 Subject: [PATCH 039/180] updates generated types --- src/queries/__generated__/WordgroupParts.ts | 55 +++++++++++ src/queries/__generated__/createWord.ts | 27 ------ src/queries/__generated__/createWordGroup.ts | 32 ------- src/queries/__generated__/insertWordGroup.ts | 36 +++++++ src/queries/__generated__/languages.ts | 17 ---- .../__generated__/subscribeWordGroupById.ts | 66 +++++++++++++ .../__generated__/subscribeWordGroups.ts | 62 ++++++++++++ src/queries/__generated__/updateARWord.ts | 30 ------ src/queries/__generated__/updateCHWord.ts | 30 ------ src/queries/__generated__/updateDEWord.ts | 30 ------ src/queries/__generated__/updateENWord.ts | 30 ------ src/queries/__generated__/updateFAWord.ts | 30 ------ src/queries/__generated__/updateWordGroup.ts | 28 +++--- src/queries/__generated__/upsertWordGroup.ts | 36 +++++++ src/queries/__generated__/wordGroup.ts | 76 --------------- src/queries/__generated__/wordGroupById.ts | 76 --------------- src/queries/__generated__/wordGroups.ts | 94 ------------------- 17 files changed, 271 insertions(+), 484 deletions(-) create mode 100644 src/queries/__generated__/WordgroupParts.ts delete mode 100644 src/queries/__generated__/createWord.ts delete mode 100644 src/queries/__generated__/createWordGroup.ts create mode 100644 src/queries/__generated__/insertWordGroup.ts delete mode 100644 src/queries/__generated__/languages.ts create mode 100644 src/queries/__generated__/subscribeWordGroupById.ts create mode 100644 src/queries/__generated__/subscribeWordGroups.ts delete mode 100644 src/queries/__generated__/updateARWord.ts delete mode 100644 src/queries/__generated__/updateCHWord.ts delete mode 100644 src/queries/__generated__/updateDEWord.ts delete mode 100644 src/queries/__generated__/updateENWord.ts delete mode 100644 src/queries/__generated__/updateFAWord.ts create mode 100644 src/queries/__generated__/upsertWordGroup.ts delete mode 100644 src/queries/__generated__/wordGroup.ts delete mode 100644 src/queries/__generated__/wordGroupById.ts delete mode 100644 src/queries/__generated__/wordGroups.ts diff --git a/src/queries/__generated__/WordgroupParts.ts b/src/queries/__generated__/WordgroupParts.ts new file mode 100644 index 0000000..6605afd --- /dev/null +++ b/src/queries/__generated__/WordgroupParts.ts @@ -0,0 +1,55 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +// ==================================================== +// GraphQL fragment: WordgroupParts +// ==================================================== + +export interface WordgroupParts_words_word_translations_language { + __typename: "api_language"; + code: string; + name: string; +} + +export interface WordgroupParts_words_word_translations { + __typename: "api_wordtranslation"; + audio: string | null; + exampleSentence: string | null; + /** + * An object relationship + */ + language: WordgroupParts_words_word_translations_language; + text: string; + id: any; +} + +export interface WordgroupParts_words_word { + __typename: "api_word"; + id: any; + /** + * An array relationship + */ + translations: WordgroupParts_words_word_translations[]; +} + +export interface WordgroupParts_words { + __typename: "api_wordgroup_words"; + id: number; + /** + * An object relationship + */ + word: WordgroupParts_words_word; +} + +export interface WordgroupParts { + __typename: "api_wordgroup"; + parentChapterId: any; + id: any; + titleCh: string; + titleDe: string; + /** + * An array relationship + */ + words: WordgroupParts_words[]; +} diff --git a/src/queries/__generated__/createWord.ts b/src/queries/__generated__/createWord.ts deleted file mode 100644 index 121b93f..0000000 --- a/src/queries/__generated__/createWord.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -// This file was automatically generated and should not be edited. - -import { IntroduceWordInput } from "./../../__generated__/globalTypes"; - -// ==================================================== -// GraphQL mutation operation: createWord -// ==================================================== - -export interface createWord_createWord_word { - __typename: "WordType"; - id: string; -} - -export interface createWord_createWord { - __typename: "IntroduceWordPayload"; - word: createWord_createWord_word | null; -} - -export interface createWord { - createWord: createWord_createWord | null; -} - -export interface createWordVariables { - input: IntroduceWordInput; -} diff --git a/src/queries/__generated__/createWordGroup.ts b/src/queries/__generated__/createWordGroup.ts deleted file mode 100644 index 8259aae..0000000 --- a/src/queries/__generated__/createWordGroup.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -// This file was automatically generated and should not be edited. - -import { IntroduceWordGroupInput } from "./../../__generated__/globalTypes"; - -// ==================================================== -// GraphQL mutation operation: createWordGroup -// ==================================================== - -export interface createWordGroup_createWordGroup_wordGroup { - __typename: "WordGroupType"; - /** - * The ID of the object. - */ - id: string; - titleCh: string; - titleDe: string; -} - -export interface createWordGroup_createWordGroup { - __typename: "IntroduceWordGroupPayload"; - wordGroup: createWordGroup_createWordGroup_wordGroup | null; -} - -export interface createWordGroup { - createWordGroup: createWordGroup_createWordGroup | null; -} - -export interface createWordGroupVariables { - input: IntroduceWordGroupInput; -} diff --git a/src/queries/__generated__/insertWordGroup.ts b/src/queries/__generated__/insertWordGroup.ts new file mode 100644 index 0000000..55aa9cf --- /dev/null +++ b/src/queries/__generated__/insertWordGroup.ts @@ -0,0 +1,36 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +import { api_wordgroup_insert_input } from "./../../__generated__/globalTypes"; + +// ==================================================== +// GraphQL mutation operation: insertWordGroup +// ==================================================== + +export interface insertWordGroup_insert_api_wordgroup_returning { + __typename: "api_wordgroup"; + id: any; + parentChapterId: any; + titleCh: string; + titleDe: string; +} + +export interface insertWordGroup_insert_api_wordgroup { + __typename: "api_wordgroup_mutation_response"; + /** + * data of the affected rows by the mutation + */ + returning: insertWordGroup_insert_api_wordgroup_returning[]; +} + +export interface insertWordGroup { + /** + * insert data into the table: "api_wordgroup" + */ + insert_api_wordgroup: insertWordGroup_insert_api_wordgroup | null; +} + +export interface insertWordGroupVariables { + input: api_wordgroup_insert_input[]; +} diff --git a/src/queries/__generated__/languages.ts b/src/queries/__generated__/languages.ts deleted file mode 100644 index 2dc00c7..0000000 --- a/src/queries/__generated__/languages.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -// This file was automatically generated and should not be edited. - -// ==================================================== -// GraphQL query operation: languages -// ==================================================== - -export interface languages_languages { - __typename: "LanguageType"; - name: string | null; - description: string | null; -} - -export interface languages { - languages: (languages_languages | null)[] | null; -} diff --git a/src/queries/__generated__/subscribeWordGroupById.ts b/src/queries/__generated__/subscribeWordGroupById.ts new file mode 100644 index 0000000..44c1a02 --- /dev/null +++ b/src/queries/__generated__/subscribeWordGroupById.ts @@ -0,0 +1,66 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +// ==================================================== +// GraphQL subscription operation: subscribeWordGroupById +// ==================================================== + +export interface subscribeWordGroupById_wordGroup_words_word_translations_language { + __typename: "api_language"; + code: string; + name: string; +} + +export interface subscribeWordGroupById_wordGroup_words_word_translations { + __typename: "api_wordtranslation"; + audio: string | null; + exampleSentence: string | null; + /** + * An object relationship + */ + language: subscribeWordGroupById_wordGroup_words_word_translations_language; + text: string; + id: any; +} + +export interface subscribeWordGroupById_wordGroup_words_word { + __typename: "api_word"; + id: any; + /** + * An array relationship + */ + translations: subscribeWordGroupById_wordGroup_words_word_translations[]; +} + +export interface subscribeWordGroupById_wordGroup_words { + __typename: "api_wordgroup_words"; + id: number; + /** + * An object relationship + */ + word: subscribeWordGroupById_wordGroup_words_word; +} + +export interface subscribeWordGroupById_wordGroup { + __typename: "api_wordgroup"; + parentChapterId: any; + id: any; + titleCh: string; + titleDe: string; + /** + * An array relationship + */ + words: subscribeWordGroupById_wordGroup_words[]; +} + +export interface subscribeWordGroupById { + /** + * fetch data from the table: "api_wordgroup" using primary key columns + */ + wordGroup: subscribeWordGroupById_wordGroup | null; +} + +export interface subscribeWordGroupByIdVariables { + id: any; +} diff --git a/src/queries/__generated__/subscribeWordGroups.ts b/src/queries/__generated__/subscribeWordGroups.ts new file mode 100644 index 0000000..e79532c --- /dev/null +++ b/src/queries/__generated__/subscribeWordGroups.ts @@ -0,0 +1,62 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +// ==================================================== +// GraphQL subscription operation: subscribeWordGroups +// ==================================================== + +export interface subscribeWordGroups_wordGroups_words_word_translations_language { + __typename: "api_language"; + code: string; + name: string; +} + +export interface subscribeWordGroups_wordGroups_words_word_translations { + __typename: "api_wordtranslation"; + audio: string | null; + exampleSentence: string | null; + /** + * An object relationship + */ + language: subscribeWordGroups_wordGroups_words_word_translations_language; + text: string; + id: any; +} + +export interface subscribeWordGroups_wordGroups_words_word { + __typename: "api_word"; + id: any; + /** + * An array relationship + */ + translations: subscribeWordGroups_wordGroups_words_word_translations[]; +} + +export interface subscribeWordGroups_wordGroups_words { + __typename: "api_wordgroup_words"; + id: number; + /** + * An object relationship + */ + word: subscribeWordGroups_wordGroups_words_word; +} + +export interface subscribeWordGroups_wordGroups { + __typename: "api_wordgroup"; + parentChapterId: any; + id: any; + titleCh: string; + titleDe: string; + /** + * An array relationship + */ + words: subscribeWordGroups_wordGroups_words[]; +} + +export interface subscribeWordGroups { + /** + * fetch data from the table: "api_wordgroup" + */ + wordGroups: subscribeWordGroups_wordGroups[]; +} diff --git a/src/queries/__generated__/updateARWord.ts b/src/queries/__generated__/updateARWord.ts deleted file mode 100644 index afea3aa..0000000 --- a/src/queries/__generated__/updateARWord.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -// This file was automatically generated and should not be edited. - -import { UpdateWordARInput } from "./../../__generated__/globalTypes"; - -// ==================================================== -// GraphQL mutation operation: updateARWord -// ==================================================== - -export interface updateARWord_updateArWord_word { - __typename: "WordARType"; - id: string; - text: string; - exampleSentence: string | null; - audio: string | null; -} - -export interface updateARWord_updateArWord { - __typename: "UpdateWordARPayload"; - word: updateARWord_updateArWord_word | null; -} - -export interface updateARWord { - updateArWord: updateARWord_updateArWord | null; -} - -export interface updateARWordVariables { - input: UpdateWordARInput; -} diff --git a/src/queries/__generated__/updateCHWord.ts b/src/queries/__generated__/updateCHWord.ts deleted file mode 100644 index bacf446..0000000 --- a/src/queries/__generated__/updateCHWord.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -// This file was automatically generated and should not be edited. - -import { UpdateWordCHInput } from "./../../__generated__/globalTypes"; - -// ==================================================== -// GraphQL mutation operation: updateCHWord -// ==================================================== - -export interface updateCHWord_updateChWord_word { - __typename: "WordCHType"; - id: string; - text: string; - exampleSentence: string | null; - audio: string | null; -} - -export interface updateCHWord_updateChWord { - __typename: "UpdateWordCHPayload"; - word: updateCHWord_updateChWord_word | null; -} - -export interface updateCHWord { - updateChWord: updateCHWord_updateChWord | null; -} - -export interface updateCHWordVariables { - input: UpdateWordCHInput; -} diff --git a/src/queries/__generated__/updateDEWord.ts b/src/queries/__generated__/updateDEWord.ts deleted file mode 100644 index 57bca02..0000000 --- a/src/queries/__generated__/updateDEWord.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -// This file was automatically generated and should not be edited. - -import { UpdateWordDEInput } from "./../../__generated__/globalTypes"; - -// ==================================================== -// GraphQL mutation operation: updateDEWord -// ==================================================== - -export interface updateDEWord_updateDeWord_word { - __typename: "WordDEType"; - id: string; - text: string; - exampleSentence: string | null; - audio: string | null; -} - -export interface updateDEWord_updateDeWord { - __typename: "UpdateWordDEPayload"; - word: updateDEWord_updateDeWord_word | null; -} - -export interface updateDEWord { - updateDeWord: updateDEWord_updateDeWord | null; -} - -export interface updateDEWordVariables { - input: UpdateWordDEInput; -} diff --git a/src/queries/__generated__/updateENWord.ts b/src/queries/__generated__/updateENWord.ts deleted file mode 100644 index 2da0a2f..0000000 --- a/src/queries/__generated__/updateENWord.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -// This file was automatically generated and should not be edited. - -import { UpdateWordENInput } from "./../../__generated__/globalTypes"; - -// ==================================================== -// GraphQL mutation operation: updateENWord -// ==================================================== - -export interface updateENWord_updateEnWord_word { - __typename: "WordENType"; - id: string; - text: string; - exampleSentence: string | null; - audio: string | null; -} - -export interface updateENWord_updateEnWord { - __typename: "UpdateWordENPayload"; - word: updateENWord_updateEnWord_word | null; -} - -export interface updateENWord { - updateEnWord: updateENWord_updateEnWord | null; -} - -export interface updateENWordVariables { - input: UpdateWordENInput; -} diff --git a/src/queries/__generated__/updateFAWord.ts b/src/queries/__generated__/updateFAWord.ts deleted file mode 100644 index f4df6ff..0000000 --- a/src/queries/__generated__/updateFAWord.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -// This file was automatically generated and should not be edited. - -import { UpdateWordFAInput } from "./../../__generated__/globalTypes"; - -// ==================================================== -// GraphQL mutation operation: updateFAWord -// ==================================================== - -export interface updateFAWord_updateFaWord_word { - __typename: "WordFAType"; - id: string; - text: string; - exampleSentence: string | null; - audio: string | null; -} - -export interface updateFAWord_updateFaWord { - __typename: "UpdateWordFAPayload"; - word: updateFAWord_updateFaWord_word | null; -} - -export interface updateFAWord { - updateFaWord: updateFAWord_updateFaWord | null; -} - -export interface updateFAWordVariables { - input: UpdateWordFAInput; -} diff --git a/src/queries/__generated__/updateWordGroup.ts b/src/queries/__generated__/updateWordGroup.ts index 237c39c..46ef0af 100644 --- a/src/queries/__generated__/updateWordGroup.ts +++ b/src/queries/__generated__/updateWordGroup.ts @@ -2,31 +2,35 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. -import { UpdateWordGroupInput } from "./../../__generated__/globalTypes"; +import { api_wordgroup_set_input } from "./../../__generated__/globalTypes"; // ==================================================== // GraphQL mutation operation: updateWordGroup // ==================================================== -export interface updateWordGroup_updateWordGroup_wordGroup { - __typename: "WordGroupType"; - /** - * The ID of the object. - */ - id: string; +export interface updateWordGroup_update_api_wordgroup_returning { + __typename: "api_wordgroup"; + id: any; titleCh: string; titleDe: string; } -export interface updateWordGroup_updateWordGroup { - __typename: "UpdateWordGroupPayload"; - wordGroup: updateWordGroup_updateWordGroup_wordGroup | null; +export interface updateWordGroup_update_api_wordgroup { + __typename: "api_wordgroup_mutation_response"; + /** + * data of the affected rows by the mutation + */ + returning: updateWordGroup_update_api_wordgroup_returning[]; } export interface updateWordGroup { - updateWordGroup: updateWordGroup_updateWordGroup | null; + /** + * update data of the table: "api_wordgroup" + */ + update_api_wordgroup: updateWordGroup_update_api_wordgroup | null; } export interface updateWordGroupVariables { - input: UpdateWordGroupInput; + id?: any | null; + input: api_wordgroup_set_input; } diff --git a/src/queries/__generated__/upsertWordGroup.ts b/src/queries/__generated__/upsertWordGroup.ts new file mode 100644 index 0000000..517b8ce --- /dev/null +++ b/src/queries/__generated__/upsertWordGroup.ts @@ -0,0 +1,36 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +import { api_wordgroup_insert_input } from "./../../__generated__/globalTypes"; + +// ==================================================== +// GraphQL mutation operation: upsertWordGroup +// ==================================================== + +export interface upsertWordGroup_insert_api_wordgroup_returning { + __typename: "api_wordgroup"; + id: any; + parentChapterId: any; + titleCh: string; + titleDe: string; +} + +export interface upsertWordGroup_insert_api_wordgroup { + __typename: "api_wordgroup_mutation_response"; + /** + * data of the affected rows by the mutation + */ + returning: upsertWordGroup_insert_api_wordgroup_returning[]; +} + +export interface upsertWordGroup { + /** + * insert data into the table: "api_wordgroup" + */ + insert_api_wordgroup: upsertWordGroup_insert_api_wordgroup | null; +} + +export interface upsertWordGroupVariables { + input: api_wordgroup_insert_input[]; +} diff --git a/src/queries/__generated__/wordGroup.ts b/src/queries/__generated__/wordGroup.ts deleted file mode 100644 index 2e25281..0000000 --- a/src/queries/__generated__/wordGroup.ts +++ /dev/null @@ -1,76 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -// This file was automatically generated and should not be edited. - -// ==================================================== -// GraphQL query operation: wordGroup -// ==================================================== - -export interface wordGroup_wordGroup_words_wordch { - __typename: "WordCHType"; - id: string; - exampleSentence: string | null; - text: string; - audio: string | null; -} - -export interface wordGroup_wordGroup_words_wordde { - __typename: "WordDEType"; - id: string; - exampleSentence: string | null; - text: string; - audio: string | null; -} - -export interface wordGroup_wordGroup_words_worden { - __typename: "WordENType"; - id: string; - exampleSentence: string | null; - text: string; - audio: string | null; -} - -export interface wordGroup_wordGroup_words_wordar { - __typename: "WordARType"; - id: string; - exampleSentence: string | null; - text: string; - audio: string | null; -} - -export interface wordGroup_wordGroup_words_wordfa { - __typename: "WordFAType"; - id: string; - exampleSentence: string | null; - text: string; - audio: string | null; -} - -export interface wordGroup_wordGroup_words { - __typename: "WordType"; - id: string; - wordch: wordGroup_wordGroup_words_wordch | null; - wordde: wordGroup_wordGroup_words_wordde | null; - worden: wordGroup_wordGroup_words_worden | null; - wordar: wordGroup_wordGroup_words_wordar | null; - wordfa: wordGroup_wordGroup_words_wordfa | null; -} - -export interface wordGroup_wordGroup { - __typename: "WordGroupType"; - /** - * The ID of the object. - */ - id: string; - titleCh: string; - titleDe: string; - words: (wordGroup_wordGroup_words | null)[] | null; -} - -export interface wordGroup { - wordGroup: wordGroup_wordGroup | null; -} - -export interface wordGroupVariables { - id?: string | null; -} diff --git a/src/queries/__generated__/wordGroupById.ts b/src/queries/__generated__/wordGroupById.ts deleted file mode 100644 index ed593c2..0000000 --- a/src/queries/__generated__/wordGroupById.ts +++ /dev/null @@ -1,76 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -// This file was automatically generated and should not be edited. - -// ==================================================== -// GraphQL query operation: wordGroupById -// ==================================================== - -export interface wordGroupById_wordGroup_words_wordch { - __typename: "WordCHType"; - id: string; - exampleSentence: string | null; - text: string; - audio: string | null; -} - -export interface wordGroupById_wordGroup_words_wordde { - __typename: "WordDEType"; - id: string; - exampleSentence: string | null; - text: string; - audio: string | null; -} - -export interface wordGroupById_wordGroup_words_worden { - __typename: "WordENType"; - id: string; - exampleSentence: string | null; - text: string; - audio: string | null; -} - -export interface wordGroupById_wordGroup_words_wordar { - __typename: "WordARType"; - id: string; - exampleSentence: string | null; - text: string; - audio: string | null; -} - -export interface wordGroupById_wordGroup_words_wordfa { - __typename: "WordFAType"; - id: string; - exampleSentence: string | null; - text: string; - audio: string | null; -} - -export interface wordGroupById_wordGroup_words { - __typename: "WordType"; - id: string; - wordch: wordGroupById_wordGroup_words_wordch | null; - wordde: wordGroupById_wordGroup_words_wordde | null; - worden: wordGroupById_wordGroup_words_worden | null; - wordar: wordGroupById_wordGroup_words_wordar | null; - wordfa: wordGroupById_wordGroup_words_wordfa | null; -} - -export interface wordGroupById_wordGroup { - __typename: "WordGroupType"; - /** - * The ID of the object. - */ - id: string; - titleCh: string; - titleDe: string; - words: (wordGroupById_wordGroup_words | null)[] | null; -} - -export interface wordGroupById { - wordGroup: wordGroupById_wordGroup | null; -} - -export interface wordGroupByIdVariables { - id?: string | null; -} diff --git a/src/queries/__generated__/wordGroups.ts b/src/queries/__generated__/wordGroups.ts deleted file mode 100644 index 7d90ae1..0000000 --- a/src/queries/__generated__/wordGroups.ts +++ /dev/null @@ -1,94 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -// This file was automatically generated and should not be edited. - -// ==================================================== -// GraphQL query operation: wordGroups -// ==================================================== - -export interface wordGroups_wordGroups_edges_node_fkChapter { - __typename: "ChapterType"; - /** - * The ID of the object. - */ - id: string; -} - -export interface wordGroups_wordGroups_edges_node_words_wordch { - __typename: "WordCHType"; - id: string; - audio: string | null; - text: string; - exampleSentence: string | null; -} - -export interface wordGroups_wordGroups_edges_node_words_wordde { - __typename: "WordDEType"; - id: string; - audio: string | null; - text: string; - exampleSentence: string | null; -} - -export interface wordGroups_wordGroups_edges_node_words_worden { - __typename: "WordENType"; - id: string; - audio: string | null; - text: string; - exampleSentence: string | null; -} - -export interface wordGroups_wordGroups_edges_node_words_wordar { - __typename: "WordARType"; - id: string; - audio: string | null; - text: string; - exampleSentence: string | null; -} - -export interface wordGroups_wordGroups_edges_node_words_wordfa { - __typename: "WordFAType"; - id: string; - audio: string | null; - text: string; - exampleSentence: string | null; -} - -export interface wordGroups_wordGroups_edges_node_words { - __typename: "WordType"; - id: string; - wordch: wordGroups_wordGroups_edges_node_words_wordch | null; - wordde: wordGroups_wordGroups_edges_node_words_wordde | null; - worden: wordGroups_wordGroups_edges_node_words_worden | null; - wordar: wordGroups_wordGroups_edges_node_words_wordar | null; - wordfa: wordGroups_wordGroups_edges_node_words_wordfa | null; -} - -export interface wordGroups_wordGroups_edges_node { - __typename: "WordGroupType"; - /** - * The ID of the object. - */ - id: string; - titleCh: string; - titleDe: string; - fkChapter: wordGroups_wordGroups_edges_node_fkChapter; - words: (wordGroups_wordGroups_edges_node_words | null)[] | null; -} - -export interface wordGroups_wordGroups_edges { - __typename: "WordGroupTypeEdge"; - /** - * The item at the end of the edge - */ - node: wordGroups_wordGroups_edges_node | null; -} - -export interface wordGroups_wordGroups { - __typename: "WordGroupTypeConnection"; - edges: (wordGroups_wordGroups_edges | null)[]; -} - -export interface wordGroups { - wordGroups: wordGroups_wordGroups | null; -} From 346d46e31b71f90428e657068bcaf72ec875edd4 Mon Sep 17 00:00:00 2001 From: David Friederich Date: Mon, 29 Jul 2019 14:19:52 +0200 Subject: [PATCH 040/180] fix wordgroup fragment + switch to _by_pk query --- src/queries/wordgroups.ts | 50 +++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/src/queries/wordgroups.ts b/src/queries/wordgroups.ts index 90a5f81..470c805 100644 --- a/src/queries/wordgroups.ts +++ b/src/queries/wordgroups.ts @@ -1,24 +1,25 @@ import gql from "graphql-tag"; export const WORDGROUP_FRAGMENT = gql` -fragment WordgroupParts on api_wordgroup { - parentChapterId: fk_chapter_id - id - titleCh: title_ch - titleDe: title_de - words { + fragment WordgroupParts on api_wordgroup { + parentChapterId: fk_chapter_id id - word { + titleCh: title_ch + titleDe: title_de + words { id - translations { - audio - exampleSentence: example_sentence - language { - code - name - } - text + word { id + translations { + audio + exampleSentence: example_sentence + language { + code + name + } + text + id + } } } } @@ -34,8 +35,8 @@ export const GET_WORDGROUPS = gql` `; export const GET_WORDGROUP_BY_ID = gql` - subscription subscribeWordGroupById($id: uuid) { - wordGroup: api_wordgroup(where: { id: { _eq: $id } }) { + subscription subscribeWordGroupById($id: uuid!) { + wordGroup: api_wordgroup_by_pk(id: $id) { ...WordgroupParts } } @@ -67,4 +68,17 @@ export const UPDATE_WORDGROUP = gql` } `; -export const INSERT_WORD = gql``; +export const UPSERT_WORDGROUP = gql` + mutation upsertWordGroup($input: [api_wordgroup_insert_input!]!) { + insert_api_wordgroup(objects: $input) { + returning { + id + parentChapterId: fk_chapter_id + titleCh: title_ch + titleDe: title_de + } + } + } +`; + +export const UPSERT_WORD = gql``; From 68dee7afc7d808ec5b17826b99e5fd919a15af3b Mon Sep 17 00:00:00 2001 From: David Friederich Date: Mon, 29 Jul 2019 14:21:03 +0200 Subject: [PATCH 041/180] WordCard + WordGroup fixes --- src/__generated__/globalTypes.ts | 245 ++++++++++++++++++++++++++++++ src/components/WordCard.tsx | 29 ++-- src/pages/WordGroup/WordGroup.tsx | 107 +++++++------ src/privateRoutes.ts | 4 +- 4 files changed, 320 insertions(+), 65 deletions(-) diff --git a/src/__generated__/globalTypes.ts b/src/__generated__/globalTypes.ts index e015477..c363862 100644 --- a/src/__generated__/globalTypes.ts +++ b/src/__generated__/globalTypes.ts @@ -35,6 +35,97 @@ export enum ProfileLanguage { EN = "EN", } +/** + * unique or primary key constraints on table "api_language" + */ +export enum api_language_constraint { + api_language_pkey = "api_language_pkey", +} + +/** + * update columns of table "api_language" + */ +export enum api_language_update_column { + code = "code", + created = "created", + id = "id", + name = "name", + updated = "updated", +} + +/** + * unique or primary key constraints on table "api_word" + */ +export enum api_word_constraint { + api_word_pkey = "api_word_pkey", +} + +/** + * update columns of table "api_word" + */ +export enum api_word_update_column { + created = "created", + id = "id", + updated = "updated", +} + +/** + * unique or primary key constraints on table "api_wordgroup" + */ +export enum api_wordgroup_constraint { + api_wordgroup_pkey = "api_wordgroup_pkey", +} + +/** + * update columns of table "api_wordgroup" + */ +export enum api_wordgroup_update_column { + created = "created", + fk_chapter_id = "fk_chapter_id", + id = "id", + title_ch = "title_ch", + title_de = "title_de", + updated = "updated", +} + +/** + * unique or primary key constraints on table "api_wordgroup_words" + */ +export enum api_wordgroup_words_constraint { + api_wordgroup_words_pkey = "api_wordgroup_words_pkey", + api_wordgroup_words_wordgroup_id_word_id_aebfaecb_uniq = "api_wordgroup_words_wordgroup_id_word_id_aebfaecb_uniq", +} + +/** + * update columns of table "api_wordgroup_words" + */ +export enum api_wordgroup_words_update_column { + id = "id", + word_id = "word_id", + wordgroup_id = "wordgroup_id", +} + +/** + * unique or primary key constraints on table "api_wordtranslation" + */ +export enum api_wordtranslation_constraint { + api_wordtranslation_pkey = "api_wordtranslation_pkey", +} + +/** + * update columns of table "api_wordtranslation" + */ +export enum api_wordtranslation_update_column { + audio = "audio", + created = "created", + example_sentence = "example_sentence", + fk_language_id = "fk_language_id", + id = "id", + text = "text", + updated = "updated", + word_id = "word_id", +} + export interface ChapterInput { description: string; fkBelongsToId?: string | null; @@ -75,6 +166,160 @@ export interface UpdateProfileInput { username?: string | null; } +/** + * input type for inserting data into table "api_language" + */ +export interface api_language_insert_input { + code?: string | null; + created?: any | null; + id?: any | null; + name?: string | null; + updated?: any | null; +} + +/** + * input type for inserting object relation for remote table "api_language" + */ +export interface api_language_obj_rel_insert_input { + data: api_language_insert_input; + on_conflict?: api_language_on_conflict | null; +} + +/** + * on conflict condition type for table "api_language" + */ +export interface api_language_on_conflict { + constraint: api_language_constraint; + update_columns: api_language_update_column[]; +} + +/** + * input type for inserting data into table "api_word" + */ +export interface api_word_insert_input { + created?: any | null; + id?: any | null; + translations?: api_wordtranslation_arr_rel_insert_input | null; + updated?: any | null; + wordgroup?: api_wordgroup_words_arr_rel_insert_input | null; +} + +/** + * input type for inserting object relation for remote table "api_word" + */ +export interface api_word_obj_rel_insert_input { + data: api_word_insert_input; + on_conflict?: api_word_on_conflict | null; +} + +/** + * on conflict condition type for table "api_word" + */ +export interface api_word_on_conflict { + constraint: api_word_constraint; + update_columns: api_word_update_column[]; +} + +/** + * input type for inserting data into table "api_wordgroup" + */ +export interface api_wordgroup_insert_input { + created?: any | null; + fk_chapter_id?: any | null; + id?: any | null; + title_ch?: string | null; + title_de?: string | null; + updated?: any | null; + words?: api_wordgroup_words_arr_rel_insert_input | null; +} + +/** + * input type for inserting object relation for remote table "api_wordgroup" + */ +export interface api_wordgroup_obj_rel_insert_input { + data: api_wordgroup_insert_input; + on_conflict?: api_wordgroup_on_conflict | null; +} + +/** + * on conflict condition type for table "api_wordgroup" + */ +export interface api_wordgroup_on_conflict { + constraint: api_wordgroup_constraint; + update_columns: api_wordgroup_update_column[]; +} + +/** + * input type for updating data in table "api_wordgroup" + */ +export interface api_wordgroup_set_input { + created?: any | null; + fk_chapter_id?: any | null; + id?: any | null; + title_ch?: string | null; + title_de?: string | null; + updated?: any | null; +} + +/** + * input type for inserting array relation for remote table "api_wordgroup_words" + */ +export interface api_wordgroup_words_arr_rel_insert_input { + data: api_wordgroup_words_insert_input[]; + on_conflict?: api_wordgroup_words_on_conflict | null; +} + +/** + * input type for inserting data into table "api_wordgroup_words" + */ +export interface api_wordgroup_words_insert_input { + id?: number | null; + word?: api_word_obj_rel_insert_input | null; + word_id?: any | null; + wordgroup?: api_wordgroup_obj_rel_insert_input | null; + wordgroup_id?: any | null; +} + +/** + * on conflict condition type for table "api_wordgroup_words" + */ +export interface api_wordgroup_words_on_conflict { + constraint: api_wordgroup_words_constraint; + update_columns: api_wordgroup_words_update_column[]; +} + +/** + * input type for inserting array relation for remote table "api_wordtranslation" + */ +export interface api_wordtranslation_arr_rel_insert_input { + data: api_wordtranslation_insert_input[]; + on_conflict?: api_wordtranslation_on_conflict | null; +} + +/** + * input type for inserting data into table "api_wordtranslation" + */ +export interface api_wordtranslation_insert_input { + audio?: string | null; + created?: any | null; + example_sentence?: string | null; + fk_language_id?: any | null; + id?: any | null; + language?: api_language_obj_rel_insert_input | null; + text?: string | null; + updated?: any | null; + word?: api_word_obj_rel_insert_input | null; + word_id?: any | null; +} + +/** + * on conflict condition type for table "api_wordtranslation" + */ +export interface api_wordtranslation_on_conflict { + constraint: api_wordtranslation_constraint; + update_columns: api_wordtranslation_update_column[]; +} + //============================================================== // END Enums and Input Objects //============================================================== diff --git a/src/components/WordCard.tsx b/src/components/WordCard.tsx index f2661fb..33cf1eb 100644 --- a/src/components/WordCard.tsx +++ b/src/components/WordCard.tsx @@ -1,24 +1,21 @@ import * as React from "react"; -import {Link as RouterLink} from "react-router-dom"; +import { Link as RouterLink } from "react-router-dom"; -import {withStyles, WithStyles} from "@material-ui/core/styles"; +import { withStyles, WithStyles } from "@material-ui/core/styles"; import Typography from "@material-ui/core/Typography"; import Card from "@material-ui/core/Paper"; import CardContent from "@material-ui/core/CardContent"; import CardActionArea from "@material-ui/core/CardActionArea"; -import {styles} from "styles"; -import {wordGroups_wordGroups, wordGroups_wordGroups_edges_node} from "queries/__generated__/wordGroups"; -import {wordGroup_wordGroup_words} from "../queries/__generated__/wordGroup"; -import {convertGlobalToDbId} from "../helpers"; +import { styles } from "styles"; +import { subscribeWordGroupById_wordGroup_words_word } from "queries/__generated__/subscribeWordGroupById"; interface Props extends WithStyles { - word: wordGroup_wordGroup_words; + word: subscribeWordGroupById_wordGroup_words_word; id: string; } -const WordCard = ({classes, word, id}: Props) => { - +const WordCard = ({ classes, word, id }: Props) => { // Note: MUI links together with react-router-dom and Typescript are a bit tricky due to their dynamic nature // See the discussion and provided solutions here... https://github.com/mui-org/material-ui/issues/7877 // + {selectedComponent && ( + <> + + {!!newComment.length && ( + + )} + )} ); diff --git a/src/queries/__generated__/CommentParts.ts b/src/queries/__generated__/CommentParts.ts index 89cfea2..db05722 100644 --- a/src/queries/__generated__/CommentParts.ts +++ b/src/queries/__generated__/CommentParts.ts @@ -19,9 +19,11 @@ export interface CommentParts { text: string; context: string | null; active: boolean; - written: any | null; + created: any; + updated: any; /** * An object relationship */ author: CommentParts_author | null; + componentId: any; } diff --git a/src/queries/__generated__/createComment.ts b/src/queries/__generated__/createComment.ts index 04c92ce..3c5c81b 100644 --- a/src/queries/__generated__/createComment.ts +++ b/src/queries/__generated__/createComment.ts @@ -21,11 +21,13 @@ export interface createComment_insert_api_comment_returning { text: string; context: string | null; active: boolean; - written: any | null; + created: any; + updated: any; /** * An object relationship */ author: createComment_insert_api_comment_returning_author | null; + componentId: any; } export interface createComment_insert_api_comment { diff --git a/src/queries/__generated__/subscribeActiveComments.ts b/src/queries/__generated__/subscribeActiveComments.ts index d21d6bd..099ce7b 100644 --- a/src/queries/__generated__/subscribeActiveComments.ts +++ b/src/queries/__generated__/subscribeActiveComments.ts @@ -26,11 +26,13 @@ export interface subscribeActiveComments_comments_answers { text: string; context: string | null; active: boolean; - written: any | null; + created: any; + updated: any; /** * An object relationship */ author: subscribeActiveComments_comments_answers_author | null; + componentId: any; } export interface subscribeActiveComments_comments { @@ -39,11 +41,13 @@ export interface subscribeActiveComments_comments { text: string; context: string | null; active: boolean; - written: any | null; + created: any; + updated: any; /** * An object relationship */ author: subscribeActiveComments_comments_author | null; + componentId: any; /** * An array relationship */ diff --git a/src/queries/__generated__/subscribeAllComments.ts b/src/queries/__generated__/subscribeAllComments.ts index acb072c..a78f207 100644 --- a/src/queries/__generated__/subscribeAllComments.ts +++ b/src/queries/__generated__/subscribeAllComments.ts @@ -26,11 +26,13 @@ export interface subscribeAllComments_comments_answers { text: string; context: string | null; active: boolean; - written: any | null; + created: any; + updated: any; /** * An object relationship */ author: subscribeAllComments_comments_answers_author | null; + componentId: any; } export interface subscribeAllComments_comments { @@ -39,11 +41,13 @@ export interface subscribeAllComments_comments { text: string; context: string | null; active: boolean; - written: any | null; + created: any; + updated: any; /** * An object relationship */ author: subscribeAllComments_comments_author | null; + componentId: any; /** * An array relationship */ diff --git a/src/queries/comments.ts b/src/queries/comments.ts index aad0c0f..b878807 100644 --- a/src/queries/comments.ts +++ b/src/queries/comments.ts @@ -7,21 +7,23 @@ export const COMMENT_FRAGMENT = gql` text context active - written + created + updated author { id firstname lastname } + componentId: fk_component_id } ${USER_FRAGMENT} `; export const GET_ALL_COMMENTS = gql` subscription subscribeAllComments { - comments: api_comment { + comments: api_comment(where: { fk_parent_comment_id: { _is_null: true } }) { ...CommentParts - answers { + answers(order_by: { created: asc_nulls_first }) { ...CommentParts } } @@ -31,9 +33,11 @@ export const GET_ALL_COMMENTS = gql` export const GET_ACTIVE_COMMENTS = gql` subscription subscribeActiveComments { - comments: api_comment(where: { active: { _eq: true } }) { + comments: api_comment( + where: { fk_parent_comment_id: { _is_null: true }, active: { _eq: true } } + ) { ...CommentParts - answers { + answers(order_by: { created: asc_nulls_first }) { ...CommentParts } } From fdfb8943224148d14ccd60b72bddcfc446578382 Mon Sep 17 00:00:00 2001 From: David Friederich Date: Fri, 2 Aug 2019 18:03:15 +0200 Subject: [PATCH 072/180] query comments based on chapter --- src/components/Discussion.tsx | 7 +------ src/components/DiscussionList.tsx | 12 ++++++++---- src/pages/Chapter/Chapter.tsx | 4 ++++ src/queries/comments.ts | 17 +++++++++++++---- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/components/Discussion.tsx b/src/components/Discussion.tsx index c139535..3700b11 100644 --- a/src/components/Discussion.tsx +++ b/src/components/Discussion.tsx @@ -17,12 +17,7 @@ import Comment from "./Comment"; import { Divider, TextField, Button, Grid } from "@material-ui/core"; import { subscribeAllComments_comments } from "queries/__generated__/subscribeAllComments"; import { useMutation } from "react-apollo-hooks"; -import { - CREATE_COMMENT, - GET_ALL_COMMENTS, - GET_ACTIVE_COMMENTS -} from "queries/comments"; -import { getOperationName } from "apollo-link"; +import { CREATE_COMMENT } from "queries/comments"; import { useAuth } from "contexts/AuthContext"; import { createComment } from "queries/__generated__/createComment"; diff --git a/src/components/DiscussionList.tsx b/src/components/DiscussionList.tsx index 785cfc7..40f5569 100644 --- a/src/components/DiscussionList.tsx +++ b/src/components/DiscussionList.tsx @@ -51,16 +51,20 @@ const DiscussionList = ({ classes, query }: Props) => { const [newComment, setNewComment] = useState(""); const { user } = useAuth(); - const { selectedComponent } = useSelector( - state => state.contentEditor - ); + const { selectedComponent, currentChapterId } = useSelector< + TAppState, + IContentEditorState + >(state => state.contentEditor); const [createComment, { loading: mutationLoading }] = useMutation( CREATE_COMMENT ); // TODO(df): Pass variables (chapter, context) down. - const { data, loading, error } = useSubscription(query); + const { data, loading, error } = useSubscription( + query, + { variables: { chapterId: currentChapterId } } + ); const discussions = data && data.comments; function handleNewCommentInputChange( diff --git a/src/pages/Chapter/Chapter.tsx b/src/pages/Chapter/Chapter.tsx index b86123f..0577902 100644 --- a/src/pages/Chapter/Chapter.tsx +++ b/src/pages/Chapter/Chapter.tsx @@ -19,6 +19,8 @@ import SectionCardContainer from "components/SectionCardContainer"; import { Permission } from "rbac-rules"; import Can from "components/Can/Can"; import { subscribeChapterById } from "queries/__generated__/subscribeChapterById"; +import { useDispatch } from "react-redux"; +import { actions } from "reducers/contentEditorSlice"; // These can come from the router... See the route definitions interface ChapterRouterProps { @@ -37,6 +39,7 @@ interface Props const Chapter = ({ classes, match }: Props) => { const { chapterId, subChapterId } = match.params; const { t } = useTranslation(); + const dispatch = useDispatch(); // Either load directly the subchapter or else the chapter const { loading, data, error } = useSubscription( @@ -83,6 +86,7 @@ const Chapter = ({ classes, match }: Props) => { // Render the subchapter screen if (subChapterId) { + dispatch(actions.setCurrentChapter(subChapterId)); return (
diff --git a/src/queries/comments.ts b/src/queries/comments.ts index b878807..f362465 100644 --- a/src/queries/comments.ts +++ b/src/queries/comments.ts @@ -20,8 +20,13 @@ export const COMMENT_FRAGMENT = gql` `; export const GET_ALL_COMMENTS = gql` - subscription subscribeAllComments { - comments: api_comment(where: { fk_parent_comment_id: { _is_null: true } }) { + subscription subscribeAllComments($chapterId: uuid!) { + comments: api_comment( + where: { + component: { chapter: { id: { _eq: $chapterId } } } + fk_parent_comment_id: { _is_null: true } + } + ) { ...CommentParts answers(order_by: { created: asc_nulls_first }) { ...CommentParts @@ -32,9 +37,13 @@ export const GET_ALL_COMMENTS = gql` `; export const GET_ACTIVE_COMMENTS = gql` - subscription subscribeActiveComments { + subscription subscribeActiveComments($chapterId: uuid!) { comments: api_comment( - where: { fk_parent_comment_id: { _is_null: true }, active: { _eq: true } } + where: { + component: { chapter: { id: { _eq: $chapterId } } } + fk_parent_comment_id: { _is_null: true } + active: { _eq: true } + } ) { ...CommentParts answers(order_by: { created: asc_nulls_first }) { From 4bf46c4144eb7610bf01876a8cd71db050800f57 Mon Sep 17 00:00:00 2001 From: David Friederich Date: Sat, 3 Aug 2019 08:14:45 +0200 Subject: [PATCH 073/180] Fix warning messages in NewChapter --- src/pages/Chapter/NewChapter.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/pages/Chapter/NewChapter.tsx b/src/pages/Chapter/NewChapter.tsx index 31b810f..f58750c 100644 --- a/src/pages/Chapter/NewChapter.tsx +++ b/src/pages/Chapter/NewChapter.tsx @@ -58,9 +58,7 @@ const NewChapter = ({ classes, parentChapter }: Props) => { async function handleSave(values: any, actions: FormikActions) { // TODO: This verbose stuff won't be necessary anymore as soon useMutation also returns a error/loading object. try { - values.fkBelongsToId = isSubChapter - ? convertGlobalToDbId(parentChapter!.id) - : null; + values.fkBelongsToId = isSubChapter ? parentChapter!.id : null; values.languages = values.languages.join(","); await upsertChapter({ variables: { @@ -168,13 +166,14 @@ const NewChapter = ({ classes, parentChapter }: Props) => { name="languages" component={Select} multiple={true} - helperText={t("chapter:newChapterLanguageHelper")} inputProps={{ name: "languages", id: "languages" }} > {data && data.languages ? ( data.languages.map(l => l && l.code && l.name ? ( - {l.name} + + {l.name} + ) : ( ) From c5429ce7ca3032320957b05f04ac912b788f40a6 Mon Sep 17 00:00:00 2001 From: David Friederich Date: Sat, 3 Aug 2019 10:06:25 +0200 Subject: [PATCH 074/180] Fix Not rerendering route props properly on history change --- src/App.tsx | 50 ++++++++++++++++++++++++-------------------------- src/index.tsx | 6 +++++- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 9eb3c2f..e7d8c47 100755 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { BrowserRouter, Switch, Route, Redirect } from "react-router-dom"; +import { Switch, Route, Redirect } from "react-router-dom"; import CssBaseline from "@material-ui/core/CssBaseline"; @@ -21,31 +21,29 @@ const App: React.FC = () => { return (
- - {loading ? ( - - ) : ( - - {isAuthenticated && ( - } /> - )} - {!isAuthenticated && ( - - )} - {!isAuthenticated && ( -
Help
} /> - )} - {!isAuthenticated && ( - - )} - {!isAuthenticated && ( - } /> - )} - - -
- )} -
+ {loading ? ( + + ) : ( + + {isAuthenticated && ( + } /> + )} + {!isAuthenticated && ( + + )} + {!isAuthenticated && ( +
Help
} /> + )} + {!isAuthenticated && ( + + )} + {!isAuthenticated && ( + } /> + )} + + +
+ )}
); }; diff --git a/src/index.tsx b/src/index.tsx index a09724d..f7d02e9 100755 --- a/src/index.tsx +++ b/src/index.tsx @@ -16,6 +16,8 @@ import * as serviceWorker from "./serviceWorker"; import packageJson from "../package.json"; import { AuthProvider, onRedirectCallback } from "contexts/AuthContext"; import configureAppStore from "configureStore"; +import { Router } from "react-router-dom"; +import myHistory from "myHistory"; const store = configureAppStore(); @@ -40,7 +42,9 @@ ReactDOM.render( - + + + From b337c84918b45315f7ff6c786016c9abb8ebb00d Mon Sep 17 00:00:00 2001 From: David Friederich Date: Sat, 3 Aug 2019 10:07:04 +0200 Subject: [PATCH 075/180] Switching Chapter creation to Hasura. Wrapping Chapter component. --- src/pages/Chapter/Chapter.tsx | 59 +++++++++++++++++++------------- src/pages/Chapter/NewChapter.tsx | 44 +++++++++++------------- src/queries/chapters.ts | 8 ++--- 3 files changed, 59 insertions(+), 52 deletions(-) diff --git a/src/pages/Chapter/Chapter.tsx b/src/pages/Chapter/Chapter.tsx index 0577902..31a138d 100644 --- a/src/pages/Chapter/Chapter.tsx +++ b/src/pages/Chapter/Chapter.tsx @@ -21,23 +21,17 @@ import Can from "components/Can/Can"; import { subscribeChapterById } from "queries/__generated__/subscribeChapterById"; import { useDispatch } from "react-redux"; import { actions } from "reducers/contentEditorSlice"; +import { string } from "prop-types"; -// These can come from the router... See the route definitions -interface ChapterRouterProps { +interface ChapterContentProps { chapterId: string; - subChapterId: string; - componentId: string; + subChapterId: string | undefined; } -interface Props - extends RouteComponentProps, - WithStyles {} - /** - * Main chapter component. Gets the chapter id via the route allowing to create links etc. Conditionally loads either the subchapter or the main chapter + * Actually loads the content of the chapter depending whether it is a main chapter or a subchapter... */ -const Chapter = ({ classes, match }: Props) => { - const { chapterId, subChapterId } = match.params; +const ChapterContent = ({ chapterId, subChapterId }: ChapterContentProps) => { const { t } = useTranslation(); const dispatch = useDispatch(); @@ -51,18 +45,6 @@ const Chapter = ({ classes, match }: Props) => { } ); - // If its a new main chapter, don't need to query anything - // TODO: This violates React Hook rules!!!! - if (chapterId === "new") { - return ( - } - no={() => } - /> - ); - } - if (!data || !data.chapter || loading || error) return ( { /> ); - // TODO: How do we handle new subchapters? if (subChapterId === "new") { if (!data) return null; return ( @@ -131,4 +112,34 @@ const Chapter = ({ classes, match }: Props) => { ); }; +// These can come from the router... See the route definitions +interface ChapterRouterProps { + chapterId: string; + subChapterId: string; + componentId: string; +} + +interface Props + extends RouteComponentProps, + WithStyles {} + +/** + * Chapter wrapper Component, necessary to return early in case of a new chapter. Gets the chapter id via the route allowing to create links etc. + */ +const Chapter = ({ classes, match }: Props) => { + const { chapterId, subChapterId } = match.params; + + // If its a new main chapter, don't need to query anything + if (chapterId === "new") { + return ( + } + no={() => } + /> + ); + } + return ; +}; + export default withStyles(styles, { withTheme: true })(Chapter); diff --git a/src/pages/Chapter/NewChapter.tsx b/src/pages/Chapter/NewChapter.tsx index f58750c..42af5ad 100644 --- a/src/pages/Chapter/NewChapter.tsx +++ b/src/pages/Chapter/NewChapter.tsx @@ -22,7 +22,6 @@ import { UPSERT_CHAPTER } from "queries/chapters"; import i18next from "i18n"; import history from "myHistory"; import ErrorMessage from "components/ErrorMessage"; -import { convertGlobalToDbId } from "helpers"; import { GET_LANGUAGES } from "../../queries/languages"; import { getLanguages } from "queries/__generated__/getLanguages"; import { subscribeChapterById_chapter_parentChapter } from "queries/__generated__/subscribeChapterById"; @@ -35,9 +34,9 @@ export const ChapterSchema = Yup.object().shape({ description: Yup.string().max(200, i18next.t("tooLong")), titleDE: Yup.string().max(50, i18next.t("tooLong")), titleCH: Yup.string().max(50, i18next.t("tooLong")), - languages: Yup.string() - .min(1, i18next.t("tooLow")) - .max(50, i18next.t("tooHigh")) + languages: Yup.array() + .required() + .min(1, i18next.t("min")) .required(i18next.t("required")) }); @@ -47,22 +46,23 @@ interface Props extends WithStyles { const NewChapter = ({ classes, parentChapter }: Props) => { const { t } = useTranslation(); - const { data } = useQuery(GET_LANGUAGES); - - const isSubChapter = !!parentChapter; - - // TODO: Unfortunately, react-apollo-hooks doesn't support yet the error, loading object in mutations (unlike with query...) const [upsertChapter, { loading }] = useMutation(UPSERT_CHAPTER); + const isSubChapter = !!parentChapter; async function handleSave(values: any, actions: FormikActions) { // TODO: This verbose stuff won't be necessary anymore as soon useMutation also returns a error/loading object. try { - values.fkBelongsToId = isSubChapter ? parentChapter!.id : null; - values.languages = values.languages.join(","); + const input = { ...values }; + input.fk_belongs_to_id = isSubChapter ? parentChapter!.id : null; + input.languages = { + data: values.languages.map((l: string) => { + return { language_id: l }; + }) + }; await upsertChapter({ variables: { - input: { chapterData: { ...values } } + input } }); isSubChapter @@ -168,19 +168,15 @@ const NewChapter = ({ classes, parentChapter }: Props) => { multiple={true} inputProps={{ name: "languages", id: "languages" }} > - {data && data.languages ? ( - data.languages.map(l => - l && l.code && l.name ? ( - - {l.name} - - ) : ( - + {data && data.languages + ? data.languages.map(l => + l && l.code && l.name ? ( + + {l.name} + + ) : null ) - ) - ) : ( - - )} + : null} {t("chapter:newChapterLanguageHelper")} diff --git a/src/queries/chapters.ts b/src/queries/chapters.ts index 2d306ad..3657dc6 100644 --- a/src/queries/chapters.ts +++ b/src/queries/chapters.ts @@ -119,10 +119,10 @@ export const GET_CHAPTER_WORDGROUPS_BY_CHAPTER_ID = gql` `; export const UPSERT_CHAPTER = gql` - mutation createChapter($input: IntroduceChapterInput!) { - createChapter(input: $input) { - chapter { - number + mutation createChapter($input: api_chapter_insert_input!) { + insert_api_chapter(objects: [$input]) { + returning { + id } } } From 78d421e4869f30eaf8524f16b3db2e77299abb35 Mon Sep 17 00:00:00 2001 From: David Friederich Date: Sat, 3 Aug 2019 10:11:26 +0200 Subject: [PATCH 076/180] cleanup unecessary import warnings --- src/PrivateApp.tsx | 1 - src/components/Comment.tsx | 2 +- src/components/Discussion.tsx | 4 ++-- src/components/DiscussionList.tsx | 2 +- src/configureStore.ts | 2 +- src/pages/Chapter/Chapter.tsx | 1 - src/pages/Chapter/CommentsWidget.tsx | 8 -------- src/pages/Dashboard/VoggiSection.tsx | 2 +- src/pages/Settings/Settings.tsx | 3 +-- 9 files changed, 7 insertions(+), 18 deletions(-) diff --git a/src/PrivateApp.tsx b/src/PrivateApp.tsx index f6dff01..10939fa 100644 --- a/src/PrivateApp.tsx +++ b/src/PrivateApp.tsx @@ -18,7 +18,6 @@ import i18n from "i18n"; import LoadingPage from "pages/LoadingPage"; import { Role } from "rbac-rules"; import { useAuth } from "contexts/AuthContext"; -import { setUser } from "@sentry/core"; function isEmpty(obj: object) { return !obj || Object.keys(obj).length === 0; diff --git a/src/components/Comment.tsx b/src/components/Comment.tsx index a77efb4..9ea4ad2 100644 --- a/src/components/Comment.tsx +++ b/src/components/Comment.tsx @@ -49,7 +49,7 @@ interface Props extends WithStyles { } const Comment = ({ classes, data }: Props) => { - const { text = "", created = 0, updated = 0 } = data; + const { text = "", created = 0 } = data; const { firstname = "", lastname = "" } = data.author || {}; const initialLetters = `${(firstname || "-").charAt(0).toUpperCase()}${( lastname || "-" diff --git a/src/components/Discussion.tsx b/src/components/Discussion.tsx index 3700b11..0469853 100644 --- a/src/components/Discussion.tsx +++ b/src/components/Discussion.tsx @@ -19,7 +19,7 @@ import { subscribeAllComments_comments } from "queries/__generated__/subscribeAl import { useMutation } from "react-apollo-hooks"; import { CREATE_COMMENT } from "queries/comments"; import { useAuth } from "contexts/AuthContext"; -import { createComment } from "queries/__generated__/createComment"; +import { createComment as TcreateComment } from "queries/__generated__/createComment"; const styles = (theme: Theme) => createStyles({ @@ -44,7 +44,7 @@ const Discussion = ({ classes, data }: Props) => { const [anchorEl, setAnchorEl] = useState(null); const { user } = useAuth(); - const [createComment, { loading }] = useMutation( + const [createComment, { loading }] = useMutation( CREATE_COMMENT ); diff --git a/src/components/DiscussionList.tsx b/src/components/DiscussionList.tsx index 40f5569..f7eb831 100644 --- a/src/components/DiscussionList.tsx +++ b/src/components/DiscussionList.tsx @@ -1,6 +1,6 @@ import React, { useState } from "react"; import { useTranslation } from "react-i18next"; -import { useQuery, useMutation, useSubscription } from "react-apollo-hooks"; +import { useMutation, useSubscription } from "react-apollo-hooks"; import { getOperationName, DocumentNode } from "apollo-link"; import { useSelector } from "react-redux"; diff --git a/src/configureStore.ts b/src/configureStore.ts index 9ae3218..2192289 100644 --- a/src/configureStore.ts +++ b/src/configureStore.ts @@ -1,4 +1,4 @@ -import { configureStore, getDefaultMiddleware } from "redux-starter-kit"; +import { configureStore } from "redux-starter-kit"; // import monitorReducersEnhancer from './enhancers/monitorReducers' // import loggerMiddleware from './middleware/logger' diff --git a/src/pages/Chapter/Chapter.tsx b/src/pages/Chapter/Chapter.tsx index 31a138d..fe29acd 100644 --- a/src/pages/Chapter/Chapter.tsx +++ b/src/pages/Chapter/Chapter.tsx @@ -21,7 +21,6 @@ import Can from "components/Can/Can"; import { subscribeChapterById } from "queries/__generated__/subscribeChapterById"; import { useDispatch } from "react-redux"; import { actions } from "reducers/contentEditorSlice"; -import { string } from "prop-types"; interface ChapterContentProps { chapterId: string; diff --git a/src/pages/Chapter/CommentsWidget.tsx b/src/pages/Chapter/CommentsWidget.tsx index 15ec67f..cffa312 100644 --- a/src/pages/Chapter/CommentsWidget.tsx +++ b/src/pages/Chapter/CommentsWidget.tsx @@ -1,7 +1,6 @@ import * as React from "react"; import SwipeableViews from "react-swipeable-views"; import { useTranslation } from "react-i18next"; -import { useSelector } from "react-redux"; import { withStyles, @@ -14,8 +13,6 @@ import Paper from "@material-ui/core/Paper"; import { Tabs, Tab } from "@material-ui/core"; import DiscussionList from "components/DiscussionList"; import { GET_ACTIVE_COMMENTS, GET_ALL_COMMENTS } from "queries/comments"; -import { TAppState } from "reducers"; -import { IContentEditorState } from "reducers/contentEditorSlice"; const styles = (theme: Theme) => createStyles({ @@ -31,11 +28,6 @@ const CommentsWidget = ({ classes, theme }: Props) => { const { t } = useTranslation(); const [activeCommentTab, setActiveCommentTab] = React.useState(0); - const { selectedComponent, editorRole } = useSelector< - TAppState, - IContentEditorState - >(state => state.contentEditor); - function handleTabChange(event: React.ChangeEvent<{}>, newValue: number) { setActiveCommentTab(newValue); } diff --git a/src/pages/Dashboard/VoggiSection.tsx b/src/pages/Dashboard/VoggiSection.tsx index 5957dd8..91d5c54 100644 --- a/src/pages/Dashboard/VoggiSection.tsx +++ b/src/pages/Dashboard/VoggiSection.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { useQuery, useSubscription } from "react-apollo-hooks"; +import { useSubscription } from "react-apollo-hooks"; import { withStyles, WithStyles } from "@material-ui/styles"; import Grid from "@material-ui/core/Grid"; diff --git a/src/pages/Settings/Settings.tsx b/src/pages/Settings/Settings.tsx index 1928567..c793aa6 100644 --- a/src/pages/Settings/Settings.tsx +++ b/src/pages/Settings/Settings.tsx @@ -18,7 +18,6 @@ import i18next from "i18n"; import { GET_PROFILE, UPDATE_PROFILE } from "queries/profile"; import BusyOrErrorCard from "components/BusyOrErrorCard"; import { profile_profile, profile } from "queries/__generated__/profile"; -import { Role } from "rbac-rules"; import { useAuth } from "contexts/AuthContext"; export const UserSetupSchema = Yup.object().shape({ @@ -35,7 +34,7 @@ export const UserSetupSchema = Yup.object().shape({ interface Props extends WithStyles {} const Settings: React.FunctionComponent = ({ classes }) => { - const { user, changeCurrentRole } = useAuth(); + const { user } = useAuth(); const username = user && user.email; const { t, i18n } = useTranslation(); From db3a783352cb1068b27a5b68fcb96a878cbcc9a6 Mon Sep 17 00:00:00 2001 From: David Friederich Date: Sat, 3 Aug 2019 20:20:49 +0200 Subject: [PATCH 077/180] adds resolving and deletion mutations of comments --- src/__generated__/globalTypes.ts | 14 ----- src/components/Discussion.tsx | 51 ++++++++++++++++--- src/queries/__generated__/createChapter.ts | 24 +++++---- src/queries/__generated__/deleteComment.ts | 26 ++++++++++ src/queries/__generated__/resolveComment.ts | 48 +++++++++++++++++ .../__generated__/subscribeActiveComments.ts | 4 ++ .../__generated__/subscribeAllComments.ts | 4 ++ src/queries/comments.ts | 19 +++++++ 8 files changed, 159 insertions(+), 31 deletions(-) create mode 100644 src/queries/__generated__/deleteComment.ts create mode 100644 src/queries/__generated__/resolveComment.ts diff --git a/src/__generated__/globalTypes.ts b/src/__generated__/globalTypes.ts index 081b2d9..b6395e5 100644 --- a/src/__generated__/globalTypes.ts +++ b/src/__generated__/globalTypes.ts @@ -281,20 +281,6 @@ export enum api_wordtranslation_update_column { word_id = "word_id", } -export interface ChapterInput { - description: string; - fkBelongsToId?: string | null; - languages: string; - number: number; - titleCH: string; - titleDE: string; -} - -export interface IntroduceChapterInput { - chapterData?: ChapterInput | null; - clientMutationId?: string | null; -} - export interface ProfileInput { currentRole?: string | null; eventNotifications?: boolean | null; diff --git a/src/components/Discussion.tsx b/src/components/Discussion.tsx index 0469853..cb773f0 100644 --- a/src/components/Discussion.tsx +++ b/src/components/Discussion.tsx @@ -17,9 +17,15 @@ import Comment from "./Comment"; import { Divider, TextField, Button, Grid } from "@material-ui/core"; import { subscribeAllComments_comments } from "queries/__generated__/subscribeAllComments"; import { useMutation } from "react-apollo-hooks"; -import { CREATE_COMMENT } from "queries/comments"; +import { + CREATE_COMMENT, + RESOLVE_COMMENT, + DELETE_COMMENT +} from "queries/comments"; import { useAuth } from "contexts/AuthContext"; import { createComment as TcreateComment } from "queries/__generated__/createComment"; +import { resolveComment as TresolveComment } from "queries/__generated__/resolveComment"; +import { deleteComment as TdeleteComment } from "queries/__generated__/deleteComment"; const styles = (theme: Theme) => createStyles({ @@ -48,6 +54,14 @@ const Discussion = ({ classes, data }: Props) => { CREATE_COMMENT ); + const [resolveComment, { loading: resolveLoading }] = useMutation< + TresolveComment + >(RESOLVE_COMMENT); + + const [deleteComment, { loading: deleteLoading }] = useMutation< + TdeleteComment + >(DELETE_COMMENT); + function handleOpenMenu(event: React.MouseEvent) { setAnchorEl(event.currentTarget); } @@ -75,6 +89,15 @@ const Discussion = ({ classes, data }: Props) => { setReply(""); } + function handleResolveDiscussion() { + resolveComment({ variables: { id: data && data.id } }); + handleCloseMenu(); + } + + function handleDeleteDiscussion() { + deleteComment({ variables: { id: data && data.id } }); + handleCloseMenu(); + } return (
@@ -108,11 +131,7 @@ const Discussion = ({ classes, data }: Props) => { {t("submit")} ) : ( - - + { open={Boolean(anchorEl)} onClose={handleCloseMenu} > - {t("resolve")} - {t("delete")} + + {t("resolve")} + + {t("delete")} + {data && data.active ? ( + + ) : null} )}
diff --git a/src/queries/__generated__/createChapter.ts b/src/queries/__generated__/createChapter.ts index e6bf4ef..ec77972 100644 --- a/src/queries/__generated__/createChapter.ts +++ b/src/queries/__generated__/createChapter.ts @@ -2,26 +2,32 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. -import { IntroduceChapterInput } from "./../../__generated__/globalTypes"; +import { api_chapter_insert_input } from "./../../__generated__/globalTypes"; // ==================================================== // GraphQL mutation operation: createChapter // ==================================================== -export interface createChapter_createChapter_chapter { - __typename: "ChapterType"; - number: number; +export interface createChapter_insert_api_chapter_returning { + __typename: "api_chapter"; + id: any; } -export interface createChapter_createChapter { - __typename: "IntroduceChapterPayload"; - chapter: createChapter_createChapter_chapter | null; +export interface createChapter_insert_api_chapter { + __typename: "api_chapter_mutation_response"; + /** + * data of the affected rows by the mutation + */ + returning: createChapter_insert_api_chapter_returning[]; } export interface createChapter { - createChapter: createChapter_createChapter | null; + /** + * insert data into the table: "api_chapter" + */ + insert_api_chapter: createChapter_insert_api_chapter | null; } export interface createChapterVariables { - input: IntroduceChapterInput; + input: api_chapter_insert_input; } diff --git a/src/queries/__generated__/deleteComment.ts b/src/queries/__generated__/deleteComment.ts new file mode 100644 index 0000000..25683e6 --- /dev/null +++ b/src/queries/__generated__/deleteComment.ts @@ -0,0 +1,26 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +// ==================================================== +// GraphQL mutation operation: deleteComment +// ==================================================== + +export interface deleteComment_delete_api_comment { + __typename: "api_comment_mutation_response"; + /** + * number of affected rows by the mutation + */ + affected_rows: number; +} + +export interface deleteComment { + /** + * delete data from the table: "api_comment" + */ + delete_api_comment: deleteComment_delete_api_comment | null; +} + +export interface deleteCommentVariables { + id: any; +} diff --git a/src/queries/__generated__/resolveComment.ts b/src/queries/__generated__/resolveComment.ts new file mode 100644 index 0000000..2360adf --- /dev/null +++ b/src/queries/__generated__/resolveComment.ts @@ -0,0 +1,48 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +// ==================================================== +// GraphQL mutation operation: resolveComment +// ==================================================== + +export interface resolveComment_update_api_comment_returning_author { + __typename: "api_profile"; + id: any; + firstname: string; + lastname: string; +} + +export interface resolveComment_update_api_comment_returning { + __typename: "api_comment"; + id: any; + text: string; + context: string | null; + active: boolean; + created: any; + updated: any; + /** + * An object relationship + */ + author: resolveComment_update_api_comment_returning_author | null; + componentId: any; +} + +export interface resolveComment_update_api_comment { + __typename: "api_comment_mutation_response"; + /** + * data of the affected rows by the mutation + */ + returning: resolveComment_update_api_comment_returning[]; +} + +export interface resolveComment { + /** + * update data of the table: "api_comment" + */ + update_api_comment: resolveComment_update_api_comment | null; +} + +export interface resolveCommentVariables { + id: any; +} diff --git a/src/queries/__generated__/subscribeActiveComments.ts b/src/queries/__generated__/subscribeActiveComments.ts index 099ce7b..e5fb821 100644 --- a/src/queries/__generated__/subscribeActiveComments.ts +++ b/src/queries/__generated__/subscribeActiveComments.ts @@ -60,3 +60,7 @@ export interface subscribeActiveComments { */ comments: subscribeActiveComments_comments[]; } + +export interface subscribeActiveCommentsVariables { + chapterId: any; +} diff --git a/src/queries/__generated__/subscribeAllComments.ts b/src/queries/__generated__/subscribeAllComments.ts index a78f207..022d265 100644 --- a/src/queries/__generated__/subscribeAllComments.ts +++ b/src/queries/__generated__/subscribeAllComments.ts @@ -60,3 +60,7 @@ export interface subscribeAllComments { */ comments: subscribeAllComments_comments[]; } + +export interface subscribeAllCommentsVariables { + chapterId: any; +} diff --git a/src/queries/comments.ts b/src/queries/comments.ts index f362465..267ef3e 100644 --- a/src/queries/comments.ts +++ b/src/queries/comments.ts @@ -64,3 +64,22 @@ export const CREATE_COMMENT = gql` } ${COMMENT_FRAGMENT} `; + +export const RESOLVE_COMMENT = gql` + mutation resolveComment($id: uuid!) { + update_api_comment(_set: { active: false }, where: { id: { _eq: $id } }) { + returning { + ...CommentParts + } + } + } + ${COMMENT_FRAGMENT} +`; + +export const DELETE_COMMENT = gql` + mutation deleteComment($id: uuid!) { + delete_api_comment(where: { id: { _eq: $id } }) { + affected_rows + } + } +`; From 439b77528e71b29b1278a36bdf92bb84678b7bd6 Mon Sep 17 00:00:00 2001 From: David Friederich Date: Sat, 3 Aug 2019 20:30:26 +0200 Subject: [PATCH 078/180] Include all comment ids (including children) upon deletion to avoid non-cascading deletion error. --- src/components/Discussion.tsx | 4 +++- src/queries/comments.ts | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/Discussion.tsx b/src/components/Discussion.tsx index cb773f0..2bf6a71 100644 --- a/src/components/Discussion.tsx +++ b/src/components/Discussion.tsx @@ -95,7 +95,9 @@ const Discussion = ({ classes, data }: Props) => { } function handleDeleteDiscussion() { - deleteComment({ variables: { id: data && data.id } }); + const ids = data && data.answers.map(a => a.id); + ids.push(data && data.id); + deleteComment({ variables: { ids } }); handleCloseMenu(); } return ( diff --git a/src/queries/comments.ts b/src/queries/comments.ts index 267ef3e..a25ef7a 100644 --- a/src/queries/comments.ts +++ b/src/queries/comments.ts @@ -77,8 +77,8 @@ export const RESOLVE_COMMENT = gql` `; export const DELETE_COMMENT = gql` - mutation deleteComment($id: uuid!) { - delete_api_comment(where: { id: { _eq: $id } }) { + mutation deleteComment($ids: [uuid!]) { + delete_api_comment(where: { id: { _in: $ids } }) { affected_rows } } From 390f869e48840ba724c030c1936110359f21447e Mon Sep 17 00:00:00 2001 From: David Friederich Date: Mon, 5 Aug 2019 11:43:06 +0200 Subject: [PATCH 079/180] Improve dnd setup (button as draggable) --- .../ContentEditor/ComponentSelector.tsx | 1 + .../ContentEditor/ContentEditor.tsx | 43 ++++++++++--------- .../components/BaseComponent.tsx | 8 +++- src/queries/chapters.ts | 20 +++++++++ 4 files changed, 50 insertions(+), 22 deletions(-) diff --git a/src/components/ContentEditor/ComponentSelector.tsx b/src/components/ContentEditor/ComponentSelector.tsx index 339dac5..0aa94df 100644 --- a/src/components/ContentEditor/ComponentSelector.tsx +++ b/src/components/ContentEditor/ComponentSelector.tsx @@ -28,6 +28,7 @@ const styles = (theme: Theme) => interface Props extends WithStyles {} const ComponentSelector = ({ classes }: Props) => { + // TODO(df): Need to query the component types, either by top level or by const { data, loading, error } = useQuery( GET_ALL_COMPONENTTYPES ); diff --git a/src/components/ContentEditor/ContentEditor.tsx b/src/components/ContentEditor/ContentEditor.tsx index 20984b4..72591de 100644 --- a/src/components/ContentEditor/ContentEditor.tsx +++ b/src/components/ContentEditor/ContentEditor.tsx @@ -28,27 +28,30 @@ interface Props extends WithStyles { } const ContentEditor = ({ classes, data }: Props) => { - /* - const [selectedParentComponet, setSelectedParentComponent] = React.useState( - undefined - ); - */ - - // TODO: What kind of state do we have to initialize here? return ( -
- - - {provided => ( -
- - - {provided.placeholder} -
- )} -
-
-
+ + + {provided => ( +
+ + {provided.placeholder} +
+ )} +
+ + {provided => ( +
+ + {provided.placeholder} +
+ )} +
+
); }; diff --git a/src/components/ContentEditor/components/BaseComponent.tsx b/src/components/ContentEditor/components/BaseComponent.tsx index 86c2ea9..92a5200 100644 --- a/src/components/ContentEditor/components/BaseComponent.tsx +++ b/src/components/ContentEditor/components/BaseComponent.tsx @@ -1,11 +1,13 @@ import * as React from "react"; import { withStyles, WithStyles } from "@material-ui/core/styles"; +import { DragHandle } from "@material-ui/icons"; import { styles } from "styles"; import { Draggable } from "react-beautiful-dnd"; import { useDispatch } from "react-redux"; import { actions } from "reducers/contentEditorSlice"; +import { IconButton } from "@material-ui/core"; interface Props extends WithStyles { index: number; @@ -20,13 +22,15 @@ const BaseComponent = ({ classes, index, data }: Props) => { ); return ( - {provided => ( + {(provided, snapshot) => (
+ + + Text: {index}
)} diff --git a/src/queries/chapters.ts b/src/queries/chapters.ts index 3657dc6..929bd5a 100644 --- a/src/queries/chapters.ts +++ b/src/queries/chapters.ts @@ -30,6 +30,11 @@ export const COMPONENT_PART = gql` id data state + type { + id + name + icon + } texts { id translations { @@ -53,6 +58,9 @@ export const GET_CHAPTERS = gql` ${COMPONENT_PART} `; +/** + * Since recursion is explicitly not allowed in the GraphQL-Spec (is an attack vector, see: https://github.com/graphql/graphql-spec/issues/91#issuecomment-254895093) we have to explicitly model the levels + */ export const GET_CHAPTER_BY_ID = gql` subscription subscribeChapterById($id: uuid!) { chapter: api_chapter_by_pk(id: $id) { @@ -62,6 +70,18 @@ export const GET_CHAPTER_BY_ID = gql` } components { ...ComponentParts + children { + ...ComponentParts + children { + ...ComponentParts + children { + ...ComponentParts + children { + ...ComponentParts + } + } + } + } } } } From a756a2751792cdb239e0a4a84e93a78427292bf6 Mon Sep 17 00:00:00 2001 From: David Friederich Date: Mon, 5 Aug 2019 12:07:32 +0200 Subject: [PATCH 080/180] Update ComponentSelector/Types query to hasura --- src/__generated__/globalTypes.ts | 15 +- .../ContentEditor/ComponentSelector.tsx | 61 +++--- src/queries/__generated__/ComponentParts.ts | 11 ++ .../__generated__/ComponentTypeParts.ts | 7 +- src/queries/__generated__/deleteComment.ts | 2 +- .../__generated__/getAllComponentTypes.ts | 25 +-- src/queries/__generated__/getChapters.ts | 11 ++ .../__generated__/subscribeChapterById.ts | 175 ++++++++++++++++++ src/queries/componentTypes.ts | 10 +- 9 files changed, 251 insertions(+), 66 deletions(-) diff --git a/src/__generated__/globalTypes.ts b/src/__generated__/globalTypes.ts index b6395e5..f6862e9 100644 --- a/src/__generated__/globalTypes.ts +++ b/src/__generated__/globalTypes.ts @@ -424,8 +424,8 @@ export interface api_component_arr_rel_insert_input { */ export interface api_component_insert_input { chapter?: api_chapter_obj_rel_insert_input | null; + children?: api_component_arr_rel_insert_input | null; comments?: api_comment_arr_rel_insert_input | null; - componentType?: api_componenttype_obj_rel_insert_input | null; created?: any | null; data?: string | null; fk_chapter_id?: any | null; @@ -435,9 +435,10 @@ export interface api_component_insert_input { id?: any | null; lockedByUser?: api_profile_obj_rel_insert_input | null; locked_ts?: any | null; - parentComponent?: api_component_obj_rel_insert_input | null; + parent?: api_component_obj_rel_insert_input | null; state?: string | null; texts?: api_text_arr_rel_insert_input | null; + type?: api_componenttype_obj_rel_insert_input | null; updated?: any | null; } @@ -457,17 +458,27 @@ export interface api_component_on_conflict { update_columns: api_component_update_column[]; } +/** + * input type for inserting array relation for remote table "api_componenttype" + */ +export interface api_componenttype_arr_rel_insert_input { + data: api_componenttype_insert_input[]; + on_conflict?: api_componenttype_on_conflict | null; +} + /** * input type for inserting data into table "api_componenttype" */ export interface api_componenttype_insert_input { base?: boolean | null; + children?: api_componenttype_arr_rel_insert_input | null; created?: any | null; fk_parent_type_id?: any | null; icon?: string | null; id?: any | null; label?: string | null; name?: string | null; + parent?: api_componenttype_obj_rel_insert_input | null; schema?: string | null; updated?: any | null; } diff --git a/src/components/ContentEditor/ComponentSelector.tsx b/src/components/ContentEditor/ComponentSelector.tsx index 0aa94df..6f13d88 100644 --- a/src/components/ContentEditor/ComponentSelector.tsx +++ b/src/components/ContentEditor/ComponentSelector.tsx @@ -33,42 +33,39 @@ const ComponentSelector = ({ classes }: Props) => { GET_ALL_COMPONENTTYPES ); - const componentTypes = data && data.componentTypes; + const types = (data && data.types) || []; return ( - {componentTypes && - componentTypes.edges.map((c, index) => { - if (!c || !c.node) return null; - const component = c.node; - return ( - - {(provided, snapshot) => ( - - - - - {component.name} - - {component.icon} - - - - )} - - ); - })} + {types.map((component, index) => { + return ( + + {(provided, snapshot) => ( + + + + + {component.name} + + {component.icon} + + + + )} + + ); + })} ); }; diff --git a/src/queries/__generated__/ComponentParts.ts b/src/queries/__generated__/ComponentParts.ts index 23ffbc8..76d58df 100644 --- a/src/queries/__generated__/ComponentParts.ts +++ b/src/queries/__generated__/ComponentParts.ts @@ -6,6 +6,13 @@ // GraphQL fragment: ComponentParts // ==================================================== +export interface ComponentParts_type { + __typename: "api_componenttype"; + id: any; + name: string; + icon: string; +} + export interface ComponentParts_texts_translations { __typename: "api_translation"; id: any; @@ -26,6 +33,10 @@ export interface ComponentParts { id: any; data: string; state: string; + /** + * An object relationship + */ + type: ComponentParts_type; /** * An array relationship */ diff --git a/src/queries/__generated__/ComponentTypeParts.ts b/src/queries/__generated__/ComponentTypeParts.ts index 458bd47..c1483c9 100644 --- a/src/queries/__generated__/ComponentTypeParts.ts +++ b/src/queries/__generated__/ComponentTypeParts.ts @@ -7,11 +7,8 @@ // ==================================================== export interface ComponentTypeParts { - __typename: "ComponentTypeType"; - /** - * The ID of the object. - */ - id: string; + __typename: "api_componenttype"; + id: any; name: string; label: string; icon: string; diff --git a/src/queries/__generated__/deleteComment.ts b/src/queries/__generated__/deleteComment.ts index 25683e6..e7749be 100644 --- a/src/queries/__generated__/deleteComment.ts +++ b/src/queries/__generated__/deleteComment.ts @@ -22,5 +22,5 @@ export interface deleteComment { } export interface deleteCommentVariables { - id: any; + ids?: any[] | null; } diff --git a/src/queries/__generated__/getAllComponentTypes.ts b/src/queries/__generated__/getAllComponentTypes.ts index 22df128..43deb81 100644 --- a/src/queries/__generated__/getAllComponentTypes.ts +++ b/src/queries/__generated__/getAllComponentTypes.ts @@ -6,12 +6,9 @@ // GraphQL query operation: getAllComponentTypes // ==================================================== -export interface getAllComponentTypes_componentTypes_edges_node { - __typename: "ComponentTypeType"; - /** - * The ID of the object. - */ - id: string; +export interface getAllComponentTypes_types { + __typename: "api_componenttype"; + id: any; name: string; label: string; icon: string; @@ -21,19 +18,9 @@ export interface getAllComponentTypes_componentTypes_edges_node { updated: any; } -export interface getAllComponentTypes_componentTypes_edges { - __typename: "ComponentTypeTypeEdge"; +export interface getAllComponentTypes { /** - * The item at the end of the edge + * fetch data from the table: "api_componenttype" */ - node: getAllComponentTypes_componentTypes_edges_node | null; -} - -export interface getAllComponentTypes_componentTypes { - __typename: "ComponentTypeTypeConnection"; - edges: (getAllComponentTypes_componentTypes_edges | null)[]; -} - -export interface getAllComponentTypes { - componentTypes: getAllComponentTypes_componentTypes | null; + types: getAllComponentTypes_types[]; } diff --git a/src/queries/__generated__/getChapters.ts b/src/queries/__generated__/getChapters.ts index 62eca18..6d2f298 100644 --- a/src/queries/__generated__/getChapters.ts +++ b/src/queries/__generated__/getChapters.ts @@ -23,6 +23,13 @@ export interface getChapters_chapters_subChapters { description: string; } +export interface getChapters_chapters_components_type { + __typename: "api_componenttype"; + id: any; + name: string; + icon: string; +} + export interface getChapters_chapters_components_texts_translations { __typename: "api_translation"; id: any; @@ -43,6 +50,10 @@ export interface getChapters_chapters_components { id: any; data: string; state: string; + /** + * An object relationship + */ + type: getChapters_chapters_components_type; /** * An array relationship */ diff --git a/src/queries/__generated__/subscribeChapterById.ts b/src/queries/__generated__/subscribeChapterById.ts index 22ebcfc..a8192b7 100644 --- a/src/queries/__generated__/subscribeChapterById.ts +++ b/src/queries/__generated__/subscribeChapterById.ts @@ -51,6 +51,13 @@ export interface subscribeChapterById_chapter_subChapters { subChapters: subscribeChapterById_chapter_subChapters_subChapters[]; } +export interface subscribeChapterById_chapter_components_type { + __typename: "api_componenttype"; + id: any; + name: string; + icon: string; +} + export interface subscribeChapterById_chapter_components_texts_translations { __typename: "api_translation"; id: any; @@ -66,15 +73,183 @@ export interface subscribeChapterById_chapter_components_texts { translations: subscribeChapterById_chapter_components_texts_translations[]; } +export interface subscribeChapterById_chapter_components_children_type { + __typename: "api_componenttype"; + id: any; + name: string; + icon: string; +} + +export interface subscribeChapterById_chapter_components_children_texts_translations { + __typename: "api_translation"; + id: any; + textField: string; +} + +export interface subscribeChapterById_chapter_components_children_texts { + __typename: "api_text"; + id: any; + /** + * An array relationship + */ + translations: subscribeChapterById_chapter_components_children_texts_translations[]; +} + +export interface subscribeChapterById_chapter_components_children_children_type { + __typename: "api_componenttype"; + id: any; + name: string; + icon: string; +} + +export interface subscribeChapterById_chapter_components_children_children_texts_translations { + __typename: "api_translation"; + id: any; + textField: string; +} + +export interface subscribeChapterById_chapter_components_children_children_texts { + __typename: "api_text"; + id: any; + /** + * An array relationship + */ + translations: subscribeChapterById_chapter_components_children_children_texts_translations[]; +} + +export interface subscribeChapterById_chapter_components_children_children_children_type { + __typename: "api_componenttype"; + id: any; + name: string; + icon: string; +} + +export interface subscribeChapterById_chapter_components_children_children_children_texts_translations { + __typename: "api_translation"; + id: any; + textField: string; +} + +export interface subscribeChapterById_chapter_components_children_children_children_texts { + __typename: "api_text"; + id: any; + /** + * An array relationship + */ + translations: subscribeChapterById_chapter_components_children_children_children_texts_translations[]; +} + +export interface subscribeChapterById_chapter_components_children_children_children_children_type { + __typename: "api_componenttype"; + id: any; + name: string; + icon: string; +} + +export interface subscribeChapterById_chapter_components_children_children_children_children_texts_translations { + __typename: "api_translation"; + id: any; + textField: string; +} + +export interface subscribeChapterById_chapter_components_children_children_children_children_texts { + __typename: "api_text"; + id: any; + /** + * An array relationship + */ + translations: subscribeChapterById_chapter_components_children_children_children_children_texts_translations[]; +} + +export interface subscribeChapterById_chapter_components_children_children_children_children { + __typename: "api_component"; + id: any; + data: string; + state: string; + /** + * An object relationship + */ + type: subscribeChapterById_chapter_components_children_children_children_children_type; + /** + * An array relationship + */ + texts: subscribeChapterById_chapter_components_children_children_children_children_texts[]; +} + +export interface subscribeChapterById_chapter_components_children_children_children { + __typename: "api_component"; + id: any; + data: string; + state: string; + /** + * An object relationship + */ + type: subscribeChapterById_chapter_components_children_children_children_type; + /** + * An array relationship + */ + texts: subscribeChapterById_chapter_components_children_children_children_texts[]; + /** + * An array relationship + */ + children: subscribeChapterById_chapter_components_children_children_children_children[]; +} + +export interface subscribeChapterById_chapter_components_children_children { + __typename: "api_component"; + id: any; + data: string; + state: string; + /** + * An object relationship + */ + type: subscribeChapterById_chapter_components_children_children_type; + /** + * An array relationship + */ + texts: subscribeChapterById_chapter_components_children_children_texts[]; + /** + * An array relationship + */ + children: subscribeChapterById_chapter_components_children_children_children[]; +} + +export interface subscribeChapterById_chapter_components_children { + __typename: "api_component"; + id: any; + data: string; + state: string; + /** + * An object relationship + */ + type: subscribeChapterById_chapter_components_children_type; + /** + * An array relationship + */ + texts: subscribeChapterById_chapter_components_children_texts[]; + /** + * An array relationship + */ + children: subscribeChapterById_chapter_components_children_children[]; +} + export interface subscribeChapterById_chapter_components { __typename: "api_component"; id: any; data: string; state: string; + /** + * An object relationship + */ + type: subscribeChapterById_chapter_components_type; /** * An array relationship */ texts: subscribeChapterById_chapter_components_texts[]; + /** + * An array relationship + */ + children: subscribeChapterById_chapter_components_children[]; } export interface subscribeChapterById_chapter { diff --git a/src/queries/componentTypes.ts b/src/queries/componentTypes.ts index 9e1bcdc..707f649 100644 --- a/src/queries/componentTypes.ts +++ b/src/queries/componentTypes.ts @@ -1,7 +1,7 @@ import gql from "graphql-tag"; export const COMPONENT_TYPE_FRAGMENT = gql` - fragment ComponentTypeParts on ComponentTypeType { + fragment ComponentTypeParts on api_componenttype { id name label @@ -15,12 +15,8 @@ export const COMPONENT_TYPE_FRAGMENT = gql` export const GET_ALL_COMPONENTTYPES = gql` query getAllComponentTypes { - componentTypes { - edges { - node { - ...ComponentTypeParts - } - } + types: api_componenttype { + ...ComponentTypeParts } } ${COMPONENT_TYPE_FRAGMENT} From 32973e994fb6340c8e7a5b8dde58d12906816eb8 Mon Sep 17 00:00:00 2001 From: David Friederich Date: Mon, 5 Aug 2019 12:56:26 +0200 Subject: [PATCH 081/180] Conditionally query component-selector based on selected component --- .../ContentEditor/ComponentSelector.tsx | 54 ++++++++++++++++--- .../__generated__/getComponentTypeById.ts | 46 ++++++++++++++++ src/queries/componentTypes.ts | 12 +++++ 3 files changed, 106 insertions(+), 6 deletions(-) create mode 100644 src/queries/__generated__/getComponentTypeById.ts diff --git a/src/components/ContentEditor/ComponentSelector.tsx b/src/components/ContentEditor/ComponentSelector.tsx index 6f13d88..2e8df83 100644 --- a/src/components/ContentEditor/ComponentSelector.tsx +++ b/src/components/ContentEditor/ComponentSelector.tsx @@ -1,4 +1,5 @@ import * as React from "react"; +import { useSelector } from "react-redux"; import { withStyles, @@ -10,9 +11,30 @@ import { Grid, Card, Typography, CardContent, Icon } from "@material-ui/core"; import { Draggable } from "react-beautiful-dnd"; import { useQuery } from "react-apollo-hooks"; -import { GET_ALL_COMPONENTTYPES } from "queries/componentTypes"; +import { + GET_ALL_COMPONENTTYPES, + GET_COMPONENTTYPE_BY_ID +} from "queries/componentTypes"; import BusyOrErrorCard from "components/BusyOrErrorCard"; -import { getAllComponentTypes } from "queries/__generated__/getAllComponentTypes"; +import { + getAllComponentTypes, + getAllComponentTypes_types +} from "queries/__generated__/getAllComponentTypes"; +import { TAppState } from "reducers"; +import { IContentEditorState } from "reducers/contentEditorSlice"; +import { + getComponentTypeById, + getComponentTypeById_type_children +} from "queries/__generated__/getComponentTypeById"; + +/** + * A user-defined type-guard: See here: https://www.typescriptlang.org/docs/handbook/advanced-types.html + */ +function isByIdResult( + result: getAllComponentTypes | getComponentTypeById +): result is getComponentTypeById { + return (result as getComponentTypeById).type !== undefined; +} const styles = (theme: Theme) => createStyles({ @@ -28,16 +50,36 @@ const styles = (theme: Theme) => interface Props extends WithStyles {} const ComponentSelector = ({ classes }: Props) => { + const { selectedComponent } = useSelector( + state => state.contentEditor + ); + // TODO(df): Need to query the component types, either by top level or by - const { data, loading, error } = useQuery( - GET_ALL_COMPONENTTYPES + const { data, loading, error } = useQuery< + getAllComponentTypes | getComponentTypeById + >( + !selectedComponent ? GET_ALL_COMPONENTTYPES : GET_COMPONENTTYPE_BY_ID, + !selectedComponent + ? undefined + : { variables: { id: selectedComponent.type.id } } ); - const types = (data && data.types) || []; + let types: + | getAllComponentTypes_types + | getComponentTypeById_type_children[] = []; + if (data && !isByIdResult(data)) { + types = data.types || []; + } else if (data) { + types = (data.type && data.type.children) || []; + } return ( - + {types.map((component, index) => { return ( Date: Mon, 5 Aug 2019 14:46:24 +0200 Subject: [PATCH 082/180] ignore config files of vscode --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index a87363b..ccb032a 100755 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,4 @@ yarn-error.log* # IDE .idea +.vscode From 329c722b2caec2aa13bc1b5d966622aac36fa146 Mon Sep 17 00:00:00 2001 From: David Friederich Date: Mon, 5 Aug 2019 18:36:17 +0200 Subject: [PATCH 083/180] Basic selected-component context aware toolbar with DnD context --- .../ContentEditor/ComponentList.tsx | 35 ++++++- .../ContentEditor/ComponentSelector.tsx | 17 +++- .../ContentEditor/ContentEditor.tsx | 36 +++++-- .../components/BaseComponent.tsx | 93 +++++++++++++++---- src/queries/chapters.ts | 2 +- src/queries/componentTypes.ts | 2 +- 6 files changed, 153 insertions(+), 32 deletions(-) diff --git a/src/components/ContentEditor/ComponentList.tsx b/src/components/ContentEditor/ComponentList.tsx index 5b24428..f2466e8 100644 --- a/src/components/ContentEditor/ComponentList.tsx +++ b/src/components/ContentEditor/ComponentList.tsx @@ -1,14 +1,41 @@ import * as React from "react"; +import { List, ListSubheader } from "@material-ui/core"; + import BaseComponent from "./components/BaseComponent"; +import { subscribeChapterById_chapter_components } from "queries/__generated__/subscribeChapterById"; + +interface Props { + /** + * Specifies the depth the list resides, i.e. for top-level components it is 0 + */ + level: number; + /** + * An array of components + */ + components: any; // TODO(df): Improve typing: Somehow there is a bug and can't use: subscribeChapterById_chapter_components[]; +} /** * Wrap with React Memo to avoid rerender */ -const ComponentList = React.memo(function ComponentList({ components }: any) { - return components.map((component: any, index: number) => ( - - )); +const ComponentList = React.memo(({ level, components }) => { + return ( + + {/* TODO(df): Extract logic into this componentHeader */} + {/* */} + {components.map( + (component: subscribeChapterById_chapter_components, index: number) => ( + + ) + )} + + ); }); export default ComponentList; diff --git a/src/components/ContentEditor/ComponentSelector.tsx b/src/components/ContentEditor/ComponentSelector.tsx index 2e8df83..1b4ec6d 100644 --- a/src/components/ContentEditor/ComponentSelector.tsx +++ b/src/components/ContentEditor/ComponentSelector.tsx @@ -1,5 +1,8 @@ import * as React from "react"; import { useSelector } from "react-redux"; +import { Draggable } from "react-beautiful-dnd"; +import { useQuery } from "react-apollo-hooks"; +import classNames from "classnames"; import { withStyles, @@ -9,8 +12,6 @@ import { } from "@material-ui/core/styles"; import { Grid, Card, Typography, CardContent, Icon } from "@material-ui/core"; -import { Draggable } from "react-beautiful-dnd"; -import { useQuery } from "react-apollo-hooks"; import { GET_ALL_COMPONENTTYPES, GET_COMPONENTTYPE_BY_ID @@ -44,6 +45,9 @@ const styles = (theme: Theme) => }, item: { backgroundColor: theme.palette.grey[600] + }, + dragging: { + backgroundColor: theme.palette.grey[400] } }); @@ -54,7 +58,6 @@ const ComponentSelector = ({ classes }: Props) => { state => state.contentEditor ); - // TODO(df): Need to query the component types, either by top level or by const { data, loading, error } = useQuery< getAllComponentTypes | getComponentTypeById >( @@ -95,7 +98,13 @@ const ComponentSelector = ({ classes }: Props) => { {...provided.draggableProps} {...provided.dragHandleProps} > - + {component.name} diff --git a/src/components/ContentEditor/ContentEditor.tsx b/src/components/ContentEditor/ContentEditor.tsx index 72591de..7330c50 100644 --- a/src/components/ContentEditor/ContentEditor.tsx +++ b/src/components/ContentEditor/ContentEditor.tsx @@ -7,6 +7,11 @@ import { styles } from "styles"; import ComponentList from "./ComponentList"; import ComponentSelector from "./ComponentSelector"; import { subscribeChapterById_chapter } from "queries/__generated__/subscribeChapterById"; +import { useSelector } from "react-redux"; +import { TAppState } from "reducers"; +import { IContentEditorState } from "reducers/contentEditorSlice"; + +export const TOP_LEVEL_COMPONENT_TYPE = "top-level-component"; /** * Is called when the drag ends. Main function that handles all the logic related to DragAndDrop @@ -28,12 +33,27 @@ interface Props extends WithStyles { } const ContentEditor = ({ classes, data }: Props) => { + const { selectedComponent } = useSelector( + state => state.contentEditor + ); + + console.log("SELECTED"); + console.log( + !selectedComponent + ? TOP_LEVEL_COMPONENT_TYPE + : `${selectedComponent.type.name}-${selectedComponent.id}` + ); + return ( {provided => ( @@ -43,10 +63,14 @@ const ContentEditor = ({ classes, data }: Props) => {
)} - - {provided => ( + {/* This is the "top-level" droppable area. Note that draggables can only be dropped within a droppable with the same type! */} + + {(provided, snapshot) => (
- + {provided.placeholder}
)} diff --git a/src/components/ContentEditor/components/BaseComponent.tsx b/src/components/ContentEditor/components/BaseComponent.tsx index 92a5200..eeed956 100644 --- a/src/components/ContentEditor/components/BaseComponent.tsx +++ b/src/components/ContentEditor/components/BaseComponent.tsx @@ -1,41 +1,102 @@ import * as React from "react"; +import classNames from "classnames"; -import { withStyles, WithStyles } from "@material-ui/core/styles"; +import { makeStyles } from "@material-ui/styles"; +import { withStyles, WithStyles, Theme } from "@material-ui/core/styles"; import { DragHandle } from "@material-ui/icons"; +import { IconButton, Icon, Typography, Grid } from "@material-ui/core"; import { styles } from "styles"; -import { Draggable } from "react-beautiful-dnd"; -import { useDispatch } from "react-redux"; -import { actions } from "reducers/contentEditorSlice"; -import { IconButton } from "@material-ui/core"; +import { Draggable, Droppable } from "react-beautiful-dnd"; +import { useDispatch, useSelector } from "react-redux"; +import { actions, IContentEditorState } from "reducers/contentEditorSlice"; +import { subscribeChapterById_chapter_components } from "queries/__generated__/subscribeChapterById"; +import ComponentList from "../ComponentList"; +import { TAppState } from "reducers"; -interface Props extends WithStyles { +const useStyles = makeStyles((theme: Theme) => ({ + container: { + border: "solid", + borderWidth: 3, + borderColor: theme.palette.grey[200] + }, + selected: { + borderColor: "red" + } +})); + +interface Props { + /** + * Specifies the "depth" of the list the component belongs to + */ + level: number; index: number; - data: any; + data: subscribeChapterById_chapter_components; } -const BaseComponent = ({ classes, index, data }: Props) => { +const BaseComponent = ({ level, index, data }: Props) => { + const classes = useStyles(); const dispatch = useDispatch(); + const { selectedComponent } = useSelector( + state => state.contentEditor + ); const selectComponent = React.useCallback( () => dispatch(actions.setSelectedComponent(data)), [data, dispatch] ); + + const isSelected = + (selectedComponent && selectedComponent.id === data.id) || false; + return ( {(provided, snapshot) => ( -
- - - - Text: {index} -
+ + + + + {data.type.icon} + {data.data} + + + {data.children.length ? ( + + + {(provided, snapshot) => ( +
+ +
+ )} +
+
+ ) : null} + )}
); }; -export default withStyles(styles, { withTheme: true })(BaseComponent); +export default BaseComponent; diff --git a/src/queries/chapters.ts b/src/queries/chapters.ts index 929bd5a..7276c8b 100644 --- a/src/queries/chapters.ts +++ b/src/queries/chapters.ts @@ -68,7 +68,7 @@ export const GET_CHAPTER_BY_ID = gql` subChapters { ...ChapterHeaderParts } - components { + components(where: { fk_component_id: { _is_null: true } }) { ...ComponentParts children { ...ComponentParts diff --git a/src/queries/componentTypes.ts b/src/queries/componentTypes.ts index 5702f12..41a3877 100644 --- a/src/queries/componentTypes.ts +++ b/src/queries/componentTypes.ts @@ -15,7 +15,7 @@ export const COMPONENT_TYPE_FRAGMENT = gql` export const GET_ALL_COMPONENTTYPES = gql` query getAllComponentTypes { - types: api_componenttype { + types: api_componenttype(where: { fk_parent_type_id: { _is_null: true } }) { ...ComponentTypeParts } } From 3cf5440bf791805a644ce3221c1d1e791e79c5cf Mon Sep 17 00:00:00 2001 From: David Friederich Date: Tue, 6 Aug 2019 23:44:16 +0200 Subject: [PATCH 084/180] Selecting, unselecting components --- .../ContentEditor/ComponentList.tsx | 2 +- .../ContentEditor/ComponentSelector.tsx | 5 +- .../ContentEditor/ContentEditor.tsx | 33 ++++---- .../components/BaseComponent.tsx | 81 ++++++++++++------- src/pages/Chapter/SubChapterDetail.tsx | 2 +- 5 files changed, 77 insertions(+), 46 deletions(-) diff --git a/src/components/ContentEditor/ComponentList.tsx b/src/components/ContentEditor/ComponentList.tsx index f2466e8..3eb45bd 100644 --- a/src/components/ContentEditor/ComponentList.tsx +++ b/src/components/ContentEditor/ComponentList.tsx @@ -21,7 +21,7 @@ interface Props { */ const ComponentList = React.memo(({ level, components }) => { return ( - + {/* TODO(df): Extract logic into this componentHeader */} {/* */} {components.map( diff --git a/src/components/ContentEditor/ComponentSelector.tsx b/src/components/ContentEditor/ComponentSelector.tsx index 1b4ec6d..90bd524 100644 --- a/src/components/ContentEditor/ComponentSelector.tsx +++ b/src/components/ContentEditor/ComponentSelector.tsx @@ -40,8 +40,9 @@ function isByIdResult( const styles = (theme: Theme) => createStyles({ container: { - backgroundColor: theme.palette.grey[800], - padding: theme.spacing(2) + height: "100px", + backgroundColor: theme.palette.grey[800] + //padding: theme.spacing(2) }, item: { backgroundColor: theme.palette.grey[600] diff --git a/src/components/ContentEditor/ContentEditor.tsx b/src/components/ContentEditor/ContentEditor.tsx index 7330c50..93ea127 100644 --- a/src/components/ContentEditor/ContentEditor.tsx +++ b/src/components/ContentEditor/ContentEditor.tsx @@ -1,9 +1,9 @@ import * as React from "react"; import { DragDropContext, Droppable, DropResult } from "react-beautiful-dnd"; +import classNames from "classnames"; -import { withStyles, WithStyles } from "@material-ui/core/styles"; +import { makeStyles, Theme, Grid } from "@material-ui/core"; -import { styles } from "styles"; import ComponentList from "./ComponentList"; import ComponentSelector from "./ComponentSelector"; import { subscribeChapterById_chapter } from "queries/__generated__/subscribeChapterById"; @@ -13,6 +13,13 @@ import { IContentEditorState } from "reducers/contentEditorSlice"; export const TOP_LEVEL_COMPONENT_TYPE = "top-level-component"; +const useStyles = makeStyles((theme: Theme) => ({ + container: { + backgroundColor: "grey", + padding: theme.spacing(2) + } +})); + /** * Is called when the drag ends. Main function that handles all the logic related to DragAndDrop */ @@ -28,22 +35,16 @@ function onDragEnd(result: DropResult) { } } -interface Props extends WithStyles { +interface Props { data: subscribeChapterById_chapter; } -const ContentEditor = ({ classes, data }: Props) => { +const ContentEditor = ({ data }: Props) => { + const classes = useStyles(); const { selectedComponent } = useSelector( state => state.contentEditor ); - console.log("SELECTED"); - console.log( - !selectedComponent - ? TOP_LEVEL_COMPONENT_TYPE - : `${selectedComponent.type.name}-${selectedComponent.id}` - ); - return ( { type={TOP_LEVEL_COMPONENT_TYPE} > {(provided, snapshot) => ( -
+ {provided.placeholder} -
+ )}
); }; -export default withStyles(styles, { withTheme: true })(ContentEditor); +export default ContentEditor; diff --git a/src/components/ContentEditor/components/BaseComponent.tsx b/src/components/ContentEditor/components/BaseComponent.tsx index eeed956..26b49f1 100644 --- a/src/components/ContentEditor/components/BaseComponent.tsx +++ b/src/components/ContentEditor/components/BaseComponent.tsx @@ -2,8 +2,8 @@ import * as React from "react"; import classNames from "classnames"; import { makeStyles } from "@material-ui/styles"; -import { withStyles, WithStyles, Theme } from "@material-ui/core/styles"; -import { DragHandle } from "@material-ui/icons"; +import { Theme } from "@material-ui/core/styles"; +import { DragHandle, Clear } from "@material-ui/icons"; import { IconButton, Icon, Typography, Grid } from "@material-ui/core"; import { styles } from "styles"; @@ -17,8 +17,9 @@ import { TAppState } from "reducers"; const useStyles = makeStyles((theme: Theme) => ({ container: { border: "solid", - borderWidth: 3, - borderColor: theme.palette.grey[200] + borderWidth: 2, + borderColor: theme.palette.grey[200], + marginBottom: theme.spacing(2) }, selected: { borderColor: "red" @@ -45,9 +46,28 @@ const BaseComponent = ({ level, index, data }: Props) => { [data, dispatch] ); + const unselectComponent = React.useCallback( + () => dispatch(actions.setSelectedComponent()), + [dispatch] + ); + const isSelected = (selectedComponent && selectedComponent.id === data.id) || false; + const handleOnComponentClick = ( + event: React.MouseEvent + ) => { + event.stopPropagation(); + selectComponent(); + }; + + const handleOnComponentUnselectClick = (event: any) => { + event.stopPropagation(); + event.preventDefault(); + console.log(event); + unselectComponent(); + }; + return ( {(provided, snapshot) => ( @@ -56,43 +76,48 @@ const BaseComponent = ({ level, index, data }: Props) => { direction="row" alignItems="stretch" ref={provided.innerRef} - // spacing={2} + spacing={1} {...provided.draggableProps} className={classNames( classes.container, isSelected && classes.selected )} + onClick={handleOnComponentClick} > - - + + {data.type.icon} {data.data} + {isSelected ? ( + + + + ) : null} {data.children.length ? ( - - - {(provided, snapshot) => ( -
- -
- )} -
-
- ) : null} + // + + {(provided, snapshot) => ( + + + + )} + + ) : // + null}
)}
diff --git a/src/pages/Chapter/SubChapterDetail.tsx b/src/pages/Chapter/SubChapterDetail.tsx index d8bdef4..b1b0f8b 100644 --- a/src/pages/Chapter/SubChapterDetail.tsx +++ b/src/pages/Chapter/SubChapterDetail.tsx @@ -55,7 +55,7 @@ const SubChapterDetail = ({ classes, data }: Props) => { ); return ( -
+
From fddfbf9639f03c36d42c4e00f6d0af2886004fea Mon Sep 17 00:00:00 2001 From: David Friederich Date: Wed, 7 Aug 2019 17:18:25 +0200 Subject: [PATCH 085/180] Copy & Clone Workaround for react-beautiful-dnd --- package-lock.json | 101 +++++++++++++ package.json | 2 + .../ContentEditor/ComponentSelector.tsx | 142 ++++++++++++------ .../components/BaseComponent.tsx | 1 - 4 files changed, 198 insertions(+), 48 deletions(-) diff --git a/package-lock.json b/package-lock.json index d7838a5..8247302 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1003,6 +1003,14 @@ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.7.2.tgz", "integrity": "sha512-RMtr1i6E8MXaBWwhXL3yeOU8JXRnz8GNxHvaUfVvwxokvayUY0zoBeWbKw1S9XkufmGEEdQd228pSZXFkAln8Q==" }, + "@emotion/is-prop-valid": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.2.tgz", + "integrity": "sha512-ZQIMAA2kLUWiUeMZNJDTeCwYRx1l8SQL0kHktze4COT22occKpDML1GDUXP5/sxhOMrZO8vZw773ni4H5Snrsg==", + "requires": { + "@emotion/memoize": "0.7.2" + } + }, "@emotion/memoize": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.2.tgz", @@ -1738,6 +1746,15 @@ "@types/react": "*" } }, + "@types/react-native": { + "version": "0.60.3", + "resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.60.3.tgz", + "integrity": "sha512-VjXawSlRG6l620tXB2dzYIwwXfzOZrl4kYHuX09EF0W9QFw/vwH7n7mp+0aFrwlr8ZOkWX/SZFPp6osyRpTGtg==", + "requires": { + "@types/prop-types": "*", + "@types/react": "*" + } + }, "@types/react-redux": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.1.tgz", @@ -1802,6 +1819,16 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==" }, + "@types/styled-components": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-4.1.18.tgz", + "integrity": "sha512-VrHkgvjbxQXOw0xWSUckusUUZ4y/jqN1u7kF29ngh0oE6uOrlZHleTgqeUqylQqHQIeQ8MxFb50BRHy8ju5DHg==", + "requires": { + "@types/react": "*", + "@types/react-native": "*", + "csstype": "^2.2.0" + } + }, "@types/unist": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz", @@ -2671,6 +2698,17 @@ "resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.2.tgz", "integrity": "sha512-CxwvxrZ9OirpXQ201Ec57OmGhmI8/ui/GwTDy0hSp6CmRvgRC0pSair6Z04Ck+JStA0sMPZzSJ3uE4n17EXpPQ==" }, + "babel-plugin-styled-components": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-1.10.6.tgz", + "integrity": "sha512-gyQj/Zf1kQti66100PhrCRjI5ldjaze9O0M3emXRPAN80Zsf8+e1thpTpaXJXVHXtaM4/+dJEgZHyS9Its+8SA==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-module-imports": "^7.0.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "lodash": "^4.17.11" + } + }, "babel-plugin-syntax-jsx": { "version": "6.18.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", @@ -3285,6 +3323,11 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" }, + "camelize": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", + "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" + }, "caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -4321,6 +4364,11 @@ "tiny-invariant": "^1.0.5" } }, + "css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=" + }, "css-color-names": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", @@ -4410,6 +4458,16 @@ "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" }, + "css-to-react-native": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-2.3.1.tgz", + "integrity": "sha512-yO+oEx1Lf+hDKasqQRVrAvzMCz825Huh1VMlEEDlRWyAhFb/FWb6I0KpEF1PkyKQ7NEdcx9d5M2ZEWgJAsgPvQ==", + "requires": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^3.3.0" + } + }, "css-tree": { "version": "1.0.0-alpha.28", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.28.tgz", @@ -7189,6 +7247,11 @@ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, + "is-what": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.3.1.tgz", + "integrity": "sha512-seFn10yAXy+yJlTRO+8VfiafC+0QJanGLMPTBWLrJm/QPauuchy0UXh8B6H5o9VA8BAzk0iYievt6mNp6gfaqA==" + }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -8967,6 +9030,14 @@ "readable-stream": "^2.0.1" } }, + "merge-anything": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/merge-anything/-/merge-anything-2.4.0.tgz", + "integrity": "sha512-MhJcPOEcDUIbwU0LnEfx5S9s9dfQ/KPu4g2UA5T5G1LRKS0XmpDvJ9+UUfTkfhge+nA1gStE4tJAvx6lXLs+rg==", + "requires": { + "is-what": "^3.2.4" + } + }, "merge-deep": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.2.tgz", @@ -12855,6 +12926,26 @@ "schema-utils": "^1.0.0" } }, + "styled-components": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-4.3.2.tgz", + "integrity": "sha512-NppHzIFavZ3TsIU3R1omtddJ0Bv1+j50AKh3ZWyXHuFvJq1I8qkQ5mZ7uQgD89Y8zJNx2qRo6RqAH1BmoVafHw==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@emotion/is-prop-valid": "^0.8.1", + "@emotion/unitless": "^0.7.0", + "babel-plugin-styled-components": ">= 1", + "css-to-react-native": "^2.2.2", + "memoize-one": "^5.0.0", + "merge-anything": "^2.2.4", + "prop-types": "^15.5.4", + "react-is": "^16.6.0", + "stylis": "^3.5.0", + "stylis-rule-sheet": "^0.0.10", + "supports-color": "^5.5.0" + } + }, "stylehacks": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", @@ -12877,6 +12968,16 @@ } } }, + "stylis": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-3.5.4.tgz", + "integrity": "sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q==" + }, + "stylis-rule-sheet": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz", + "integrity": "sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw==" + }, "subscriptions-transport-ws": { "version": "0.9.16", "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.16.tgz", diff --git a/package.json b/package.json index 1b93c2b..d5df559 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@types/react-beautiful-dnd": "^11.0.2", "@types/react-redux": "^7.1.1", "@types/react-select": "^3.0.0", + "@types/styled-components": "^4.1.18", "apollo-cache-inmemory": "^1.6.2", "apollo-client": "^2.6.3", "apollo-link": "^1.2.12", @@ -39,6 +40,7 @@ "react-select": "latest", "react-swipeable-views": "^0.13.3", "redux-starter-kit": "^0.6.3", + "styled-components": "^4.3.2", "subscriptions-transport-ws": "^0.9.16", "yup": "^0.27.0" }, diff --git a/src/components/ContentEditor/ComponentSelector.tsx b/src/components/ContentEditor/ComponentSelector.tsx index 90bd524..be94ccf 100644 --- a/src/components/ContentEditor/ComponentSelector.tsx +++ b/src/components/ContentEditor/ComponentSelector.tsx @@ -1,15 +1,15 @@ import * as React from "react"; import { useSelector } from "react-redux"; -import { Draggable } from "react-beautiful-dnd"; +import { + Draggable, + DraggableProvided, + DraggableStateSnapshot +} from "react-beautiful-dnd"; import { useQuery } from "react-apollo-hooks"; import classNames from "classnames"; +import styled from "styled-components"; -import { - withStyles, - WithStyles, - Theme, - createStyles -} from "@material-ui/core/styles"; +import { Theme, makeStyles } from "@material-ui/core/styles"; import { Grid, Card, Typography, CardContent, Icon } from "@material-ui/core"; import { @@ -28,6 +28,78 @@ import { getComponentTypeById_type_children } from "queries/__generated__/getComponentTypeById"; +const useStyles = makeStyles((theme: Theme) => ({ + container: { + height: "100px", + backgroundColor: theme.palette.grey[800] + //padding: theme.spacing(2) + }, + item: { + backgroundColor: theme.palette.grey[600] + }, + dragging: { + backgroundColor: theme.palette.grey[400] + } +})); + +const Clone = styled.div` + ~ div { + transform: none !important; + } +`; + +type ComponentTypeItemProps = { + item: getComponentTypeById_type_children; + provided?: DraggableProvided; + snapshot?: DraggableStateSnapshot; +}; + +const ComponentTypeItem = ({ + item, + provided, + snapshot +}: ComponentTypeItemProps) => { + const classes = useStyles(); + + const content = ( + + + + {item.name} + + {item.icon} + + + ); + + if (provided) { + return ( + + {content} + + ); + } + return ( + + + {content} + + + ); +}; + /** * A user-defined type-guard: See here: https://www.typescriptlang.org/docs/handbook/advanced-types.html */ @@ -37,24 +109,10 @@ function isByIdResult( return (result as getComponentTypeById).type !== undefined; } -const styles = (theme: Theme) => - createStyles({ - container: { - height: "100px", - backgroundColor: theme.palette.grey[800] - //padding: theme.spacing(2) - }, - item: { - backgroundColor: theme.palette.grey[600] - }, - dragging: { - backgroundColor: theme.palette.grey[400] - } - }); - -interface Props extends WithStyles {} +interface Props {} -const ComponentSelector = ({ classes }: Props) => { +const ComponentSelector = ({ }: Props) => { + const classes = useStyles(); const { selectedComponent } = useSelector( state => state.contentEditor ); @@ -92,28 +150,18 @@ const ComponentSelector = ({ classes }: Props) => { index={index} > {(provided, snapshot) => ( - - - - - {component.name} - - {component.icon} - - - + <> + + {/* react-beautiful-dnd unfortunately does not support Copy & Clone. + See here for the workaround with this "Clone": + https://github.com/atlassian/react-beautiful-dnd/issues/216#issuecomment-423708497 + */} + {snapshot.isDragging && } + )} ); @@ -122,4 +170,4 @@ const ComponentSelector = ({ classes }: Props) => { ); }; -export default withStyles(styles, { withTheme: true })(ComponentSelector); +export default ComponentSelector; diff --git a/src/components/ContentEditor/components/BaseComponent.tsx b/src/components/ContentEditor/components/BaseComponent.tsx index 26b49f1..aea8080 100644 --- a/src/components/ContentEditor/components/BaseComponent.tsx +++ b/src/components/ContentEditor/components/BaseComponent.tsx @@ -64,7 +64,6 @@ const BaseComponent = ({ level, index, data }: Props) => { const handleOnComponentUnselectClick = (event: any) => { event.stopPropagation(); event.preventDefault(); - console.log(event); unselectComponent(); }; From bd4330adaf80d7eccfbbb25e33e04f227568c01c Mon Sep 17 00:00:00 2001 From: David Friederich Date: Wed, 7 Aug 2019 18:01:59 +0200 Subject: [PATCH 086/180] small styling improvements --- src/components/ContentEditor/ContentEditor.tsx | 2 +- .../ContentEditor/components/BaseComponent.tsx | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/ContentEditor/ContentEditor.tsx b/src/components/ContentEditor/ContentEditor.tsx index 93ea127..fe0db36 100644 --- a/src/components/ContentEditor/ContentEditor.tsx +++ b/src/components/ContentEditor/ContentEditor.tsx @@ -16,7 +16,7 @@ export const TOP_LEVEL_COMPONENT_TYPE = "top-level-component"; const useStyles = makeStyles((theme: Theme) => ({ container: { backgroundColor: "grey", - padding: theme.spacing(2) + padding: theme.spacing(5) } })); diff --git a/src/components/ContentEditor/components/BaseComponent.tsx b/src/components/ContentEditor/components/BaseComponent.tsx index aea8080..f4715ac 100644 --- a/src/components/ContentEditor/components/BaseComponent.tsx +++ b/src/components/ContentEditor/components/BaseComponent.tsx @@ -6,7 +6,6 @@ import { Theme } from "@material-ui/core/styles"; import { DragHandle, Clear } from "@material-ui/icons"; import { IconButton, Icon, Typography, Grid } from "@material-ui/core"; -import { styles } from "styles"; import { Draggable, Droppable } from "react-beautiful-dnd"; import { useDispatch, useSelector } from "react-redux"; import { actions, IContentEditorState } from "reducers/contentEditorSlice"; @@ -22,7 +21,7 @@ const useStyles = makeStyles((theme: Theme) => ({ marginBottom: theme.spacing(2) }, selected: { - borderColor: "red" + borderColor: theme.palette.primary.light } })); @@ -68,7 +67,11 @@ const BaseComponent = ({ level, index, data }: Props) => { }; return ( - + {(provided, snapshot) => ( { style={{ padding: 20 }} > + {provided.placeholder} )} From 60ea8311306ba60d48d7229da17a23bdd4861547 Mon Sep 17 00:00:00 2001 From: David Friederich Date: Thu, 8 Aug 2019 17:13:26 +0200 Subject: [PATCH 087/180] Adds position/order in components query --- src/__generated__/globalTypes.ts | 3 + src/queries/__generated__/ComponentParts.ts | 1 + src/queries/__generated__/getChapters.ts | 1 + .../__generated__/subscribeChapterById.ts | 65 ++----------------- src/queries/chapters.ts | 18 ++--- 5 files changed, 19 insertions(+), 69 deletions(-) diff --git a/src/__generated__/globalTypes.ts b/src/__generated__/globalTypes.ts index f6862e9..6d780f5 100644 --- a/src/__generated__/globalTypes.ts +++ b/src/__generated__/globalTypes.ts @@ -83,6 +83,7 @@ export enum api_comment_update_column { */ export enum api_component_constraint { api_component_pkey = "api_component_pkey", + ordered_list_null = "ordered_list_null", } /** @@ -97,6 +98,7 @@ export enum api_component_update_column { fk_locked_by_id = "fk_locked_by_id", id = "id", locked_ts = "locked_ts", + order_in_chapter = "order_in_chapter", state = "state", updated = "updated", } @@ -435,6 +437,7 @@ export interface api_component_insert_input { id?: any | null; lockedByUser?: api_profile_obj_rel_insert_input | null; locked_ts?: any | null; + order_in_chapter?: number | null; parent?: api_component_obj_rel_insert_input | null; state?: string | null; texts?: api_text_arr_rel_insert_input | null; diff --git a/src/queries/__generated__/ComponentParts.ts b/src/queries/__generated__/ComponentParts.ts index 76d58df..52d0b4f 100644 --- a/src/queries/__generated__/ComponentParts.ts +++ b/src/queries/__generated__/ComponentParts.ts @@ -33,6 +33,7 @@ export interface ComponentParts { id: any; data: string; state: string; + position: number | null; /** * An object relationship */ diff --git a/src/queries/__generated__/getChapters.ts b/src/queries/__generated__/getChapters.ts index 6d2f298..09d7046 100644 --- a/src/queries/__generated__/getChapters.ts +++ b/src/queries/__generated__/getChapters.ts @@ -50,6 +50,7 @@ export interface getChapters_chapters_components { id: any; data: string; state: string; + position: number | null; /** * An object relationship */ diff --git a/src/queries/__generated__/subscribeChapterById.ts b/src/queries/__generated__/subscribeChapterById.ts index a8192b7..518afec 100644 --- a/src/queries/__generated__/subscribeChapterById.ts +++ b/src/queries/__generated__/subscribeChapterById.ts @@ -6,51 +6,6 @@ // GraphQL subscription operation: subscribeChapterById // ==================================================== -export interface subscribeChapterById_chapter_parentChapter { - __typename: "api_chapter"; - id: any; - number: number; - titleCH: string; - titleDE: string; - description: string; -} - -export interface subscribeChapterById_chapter_subChapters_parentChapter { - __typename: "api_chapter"; - id: any; - number: number; - titleCH: string; - titleDE: string; - description: string; -} - -export interface subscribeChapterById_chapter_subChapters_subChapters { - __typename: "api_chapter"; - id: any; - titleCH: string; - titleDE: string; - description: string; -} - -export interface subscribeChapterById_chapter_subChapters { - __typename: "api_chapter"; - id: any; - titleCH: string; - titleDE: string; - description: string; - number: number; - created: any; - updated: any; - /** - * An object relationship - */ - parentChapter: subscribeChapterById_chapter_subChapters_parentChapter | null; - /** - * An array relationship - */ - subChapters: subscribeChapterById_chapter_subChapters_subChapters[]; -} - export interface subscribeChapterById_chapter_components_type { __typename: "api_componenttype"; id: any; @@ -166,6 +121,7 @@ export interface subscribeChapterById_chapter_components_children_children_child id: any; data: string; state: string; + position: number | null; /** * An object relationship */ @@ -181,6 +137,7 @@ export interface subscribeChapterById_chapter_components_children_children_child id: any; data: string; state: string; + position: number | null; /** * An object relationship */ @@ -200,6 +157,7 @@ export interface subscribeChapterById_chapter_components_children_children { id: any; data: string; state: string; + position: number | null; /** * An object relationship */ @@ -219,6 +177,7 @@ export interface subscribeChapterById_chapter_components_children { id: any; data: string; state: string; + position: number | null; /** * An object relationship */ @@ -238,6 +197,7 @@ export interface subscribeChapterById_chapter_components { id: any; data: string; state: string; + position: number | null; /** * An object relationship */ @@ -254,21 +214,6 @@ export interface subscribeChapterById_chapter_components { export interface subscribeChapterById_chapter { __typename: "api_chapter"; - id: any; - number: number; - titleCH: string; - titleDE: string; - description: string; - created: any; - updated: any; - /** - * An object relationship - */ - parentChapter: subscribeChapterById_chapter_parentChapter | null; - /** - * An array relationship - */ - subChapters: subscribeChapterById_chapter_subChapters[]; /** * An array relationship */ diff --git a/src/queries/chapters.ts b/src/queries/chapters.ts index 7276c8b..ea6a9b8 100644 --- a/src/queries/chapters.ts +++ b/src/queries/chapters.ts @@ -30,6 +30,7 @@ export const COMPONENT_PART = gql` id data state + position: order_in_chapter type { id name @@ -64,19 +65,18 @@ export const GET_CHAPTERS = gql` export const GET_CHAPTER_BY_ID = gql` subscription subscribeChapterById($id: uuid!) { chapter: api_chapter_by_pk(id: $id) { - ...ChapterHeaderParts - subChapters { - ...ChapterHeaderParts - } - components(where: { fk_component_id: { _is_null: true } }) { + components( + where: { fk_component_id: { _is_null: true } } + order_by: { order_in_chapter: asc } + ) { ...ComponentParts - children { + children(order_by: { order_in_chapter: asc }) { ...ComponentParts - children { + children(order_by: { order_in_chapter: asc }) { ...ComponentParts - children { + children(order_by: { order_in_chapter: asc }) { ...ComponentParts - children { + children(order_by: { order_in_chapter: asc }) { ...ComponentParts } } From 8ee0157d741ae2bcac067b0d20f2cc9786ad1c30 Mon Sep 17 00:00:00 2001 From: David Friederich Date: Thu, 8 Aug 2019 17:18:00 +0200 Subject: [PATCH 088/180] adding lost chapter fragments --- .../__generated__/subscribeChapterById.ts | 60 +++++++++++++++++++ src/queries/chapters.ts | 4 ++ 2 files changed, 64 insertions(+) diff --git a/src/queries/__generated__/subscribeChapterById.ts b/src/queries/__generated__/subscribeChapterById.ts index 518afec..506532c 100644 --- a/src/queries/__generated__/subscribeChapterById.ts +++ b/src/queries/__generated__/subscribeChapterById.ts @@ -6,6 +6,51 @@ // GraphQL subscription operation: subscribeChapterById // ==================================================== +export interface subscribeChapterById_chapter_parentChapter { + __typename: "api_chapter"; + id: any; + number: number; + titleCH: string; + titleDE: string; + description: string; +} + +export interface subscribeChapterById_chapter_subChapters_parentChapter { + __typename: "api_chapter"; + id: any; + number: number; + titleCH: string; + titleDE: string; + description: string; +} + +export interface subscribeChapterById_chapter_subChapters_subChapters { + __typename: "api_chapter"; + id: any; + titleCH: string; + titleDE: string; + description: string; +} + +export interface subscribeChapterById_chapter_subChapters { + __typename: "api_chapter"; + id: any; + titleCH: string; + titleDE: string; + description: string; + number: number; + created: any; + updated: any; + /** + * An object relationship + */ + parentChapter: subscribeChapterById_chapter_subChapters_parentChapter | null; + /** + * An array relationship + */ + subChapters: subscribeChapterById_chapter_subChapters_subChapters[]; +} + export interface subscribeChapterById_chapter_components_type { __typename: "api_componenttype"; id: any; @@ -214,6 +259,21 @@ export interface subscribeChapterById_chapter_components { export interface subscribeChapterById_chapter { __typename: "api_chapter"; + id: any; + number: number; + titleCH: string; + titleDE: string; + description: string; + created: any; + updated: any; + /** + * An object relationship + */ + parentChapter: subscribeChapterById_chapter_parentChapter | null; + /** + * An array relationship + */ + subChapters: subscribeChapterById_chapter_subChapters[]; /** * An array relationship */ diff --git a/src/queries/chapters.ts b/src/queries/chapters.ts index ea6a9b8..4eafc4a 100644 --- a/src/queries/chapters.ts +++ b/src/queries/chapters.ts @@ -65,6 +65,10 @@ export const GET_CHAPTERS = gql` export const GET_CHAPTER_BY_ID = gql` subscription subscribeChapterById($id: uuid!) { chapter: api_chapter_by_pk(id: $id) { + ...ChapterHeaderParts + subChapters { + ...ChapterHeaderParts + } components( where: { fk_component_id: { _is_null: true } } order_by: { order_in_chapter: asc } From 7ff3d5d34600dfe8da0f716f67a56e77f0a1cb2c Mon Sep 17 00:00:00 2001 From: David Friederich Date: Thu, 8 Aug 2019 17:38:45 +0200 Subject: [PATCH 089/180] Move OnDragEnd into func component --- .../ContentEditor/ContentEditor.tsx | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/components/ContentEditor/ContentEditor.tsx b/src/components/ContentEditor/ContentEditor.tsx index fe0db36..82ebb94 100644 --- a/src/components/ContentEditor/ContentEditor.tsx +++ b/src/components/ContentEditor/ContentEditor.tsx @@ -20,21 +20,6 @@ const useStyles = makeStyles((theme: Theme) => ({ } })); -/** - * Is called when the drag ends. Main function that handles all the logic related to DragAndDrop - */ -function onDragEnd(result: DropResult) { - // If dropped outside of the list - if (!result.destination) { - return; - } - - // Do nothing if dropped at the same spot - if (result.destination.index === result.source.index) { - return; - } -} - interface Props { data: subscribeChapterById_chapter; } @@ -45,6 +30,21 @@ const ContentEditor = ({ data }: Props) => { state => state.contentEditor ); + /** + * Is called when the drag ends. Main function that handles all the logic related to DragAndDrop + */ + function onDragEnd(result: DropResult) { + // If dropped outside of the list + if (!result.destination) { + return; + } + + // Do nothing if dropped at the same spot + if (result.destination.index === result.source.index) { + return; + } + } + return ( Date: Thu, 8 Aug 2019 17:52:37 +0200 Subject: [PATCH 090/180] add react-apollo v3 (now @apollo/react-hooks) and remove old 2 libs --- package-lock.json | 61 ++++++++++++++++++++++++++--------------------- package.json | 3 +-- src/index.tsx | 15 +++++------- 3 files changed, 41 insertions(+), 38 deletions(-) diff --git a/package-lock.json b/package-lock.json index d7838a5..2446145 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,40 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@apollo/react-common": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@apollo/react-common/-/react-common-3.0.0.tgz", + "integrity": "sha512-EqHASkcmxipy2hU8rja+lD1S1HoTdodKKyJZZ3dgewnAHXnzXnnC3rw1+lkrgXPFsI2n2d2N2LYisD79OCdPmw==", + "requires": { + "ts-invariant": "^0.4.4", + "tslib": "^1.10.0" + }, + "dependencies": { + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" + } + } + }, + "@apollo/react-hooks": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@apollo/react-hooks/-/react-hooks-3.0.0.tgz", + "integrity": "sha512-7kaV6rkx2WZjDYcBmp52oyhTxbNn5Jc4AUmsXZVEnDu9uuvNYURA8bLlJNF8yu4zS7ed8D+ZebC0y0bhrz8wIg==", + "requires": { + "@apollo/react-common": "^3.0.0", + "@wry/equality": "^0.1.9", + "ts-invariant": "^0.4.4", + "tslib": "^1.10.0" + }, + "dependencies": { + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" + } + } + }, "@auth0/auth0-spa-js": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@auth0/auth0-spa-js/-/auth0-spa-js-1.1.1.tgz", @@ -8776,11 +8810,6 @@ "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=" }, - "lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" - }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -11077,28 +11106,6 @@ "scheduler": "^0.13.6" } }, - "react-apollo": { - "version": "2.5.8", - "resolved": "https://registry.npmjs.org/react-apollo/-/react-apollo-2.5.8.tgz", - "integrity": "sha512-60yOQrnNosxU/tRbOxGDaYNLFcOKmQqxHPhxyvKTlGIaF/rRCXQRKixUgWVffpEupSHHD7psY5k5ZOuZsdsSGQ==", - "requires": { - "apollo-utilities": "^1.3.0", - "fast-json-stable-stringify": "^2.0.0", - "hoist-non-react-statics": "^3.3.0", - "lodash.isequal": "^4.5.0", - "prop-types": "^15.7.2", - "ts-invariant": "^0.4.2", - "tslib": "^1.9.3" - } - }, - "react-apollo-hooks": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/react-apollo-hooks/-/react-apollo-hooks-0.5.0.tgz", - "integrity": "sha512-Us5KqFe7/c6vY1NaiyfhnD2Pz4lPLTojQXLppShaBVYU/vYvJrRjmj4MzIPXnExXaSfnQ+K2bWDr4lP4efbsRQ==", - "requires": { - "lodash": "^4.17.11" - } - }, "react-app-polyfill": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-1.0.1.tgz", diff --git a/package.json b/package.json index 1b93c2b..cf7f943 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "@apollo/react-hooks": "^3.0.0", "@auth0/auth0-spa-js": "^1.1.1", "@material-ui/core": "^4.3.0", "@material-ui/icons": "^4.2.1", @@ -29,8 +30,6 @@ "i18next": "^17.0.6", "i18next-browser-languagedetector": "^3.0.1", "react": "^16.8.6", - "react-apollo": "^2.5.8", - "react-apollo-hooks": "^0.5.0", "react-beautiful-dnd": "^11.0.5", "react-dom": "^16.8.6", "react-i18next": "^10.11.4", diff --git a/src/index.tsx b/src/index.tsx index f7d02e9..267c1e5 100755 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,9 +1,8 @@ import React from "react"; import ReactDOM from "react-dom"; -import { ApolloProvider } from "react-apollo"; -import { ApolloProvider as ApolloHooksProvider } from "react-apollo-hooks"; import * as Sentry from "@sentry/browser"; import { Provider } from "react-redux"; +import { ApolloProvider } from "@apollo/react-hooks"; import { ThemeProvider } from "@material-ui/styles"; @@ -40,13 +39,11 @@ ReactDOM.render( onRedirectCallback={onRedirectCallback} > - - - - - - - + + + + + , From 759d649fe8af2c4609145459718a626116cb23a7 Mon Sep 17 00:00:00 2001 From: David Friederich Date: Thu, 8 Aug 2019 17:52:47 +0200 Subject: [PATCH 091/180] Updated imports --- src/PrivateApp.tsx | 2 +- src/components/ContentEditor/ComponentSelector.tsx | 2 +- src/components/Discussion.tsx | 2 +- src/components/DiscussionList.tsx | 2 +- src/pages/Chapter/Chapter.tsx | 2 +- src/pages/Chapter/Chapters.tsx | 2 +- src/pages/Chapter/NewChapter.tsx | 2 +- src/pages/Dashboard/ChapterSection.tsx | 2 +- src/pages/Dashboard/VoggiSection.tsx | 2 +- src/pages/Settings/Settings.tsx | 2 +- src/pages/SetupWizard/SetupWizard.tsx | 2 +- src/pages/WordGroup/ChapterWordGroups.tsx | 2 +- src/pages/WordGroup/WordEditor.tsx | 4 ++-- src/pages/WordGroup/WordGroup.tsx | 2 +- src/pages/WordGroup/WordGroupEditor.tsx | 2 +- src/pages/WordGroup/WordGroups.tsx | 2 +- 16 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/PrivateApp.tsx b/src/PrivateApp.tsx index 10939fa..886f8bd 100644 --- a/src/PrivateApp.tsx +++ b/src/PrivateApp.tsx @@ -1,6 +1,6 @@ import React from "react"; import { Switch, Route } from "react-router"; -import { useQuery } from "react-apollo-hooks"; +import { useQuery } from "@apollo/react-hooks"; import { withStyles, WithStyles } from "@material-ui/styles"; diff --git a/src/components/ContentEditor/ComponentSelector.tsx b/src/components/ContentEditor/ComponentSelector.tsx index 339dac5..5aaac06 100644 --- a/src/components/ContentEditor/ComponentSelector.tsx +++ b/src/components/ContentEditor/ComponentSelector.tsx @@ -9,7 +9,7 @@ import { import { Grid, Card, Typography, CardContent, Icon } from "@material-ui/core"; import { Draggable } from "react-beautiful-dnd"; -import { useQuery } from "react-apollo-hooks"; +import { useQuery } from "@apollo/react-hooks"; import { GET_ALL_COMPONENTTYPES } from "queries/componentTypes"; import BusyOrErrorCard from "components/BusyOrErrorCard"; import { getAllComponentTypes } from "queries/__generated__/getAllComponentTypes"; diff --git a/src/components/Discussion.tsx b/src/components/Discussion.tsx index 2bf6a71..e9a0fe6 100644 --- a/src/components/Discussion.tsx +++ b/src/components/Discussion.tsx @@ -16,7 +16,7 @@ import MenuItem from "@material-ui/core/MenuItem"; import Comment from "./Comment"; import { Divider, TextField, Button, Grid } from "@material-ui/core"; import { subscribeAllComments_comments } from "queries/__generated__/subscribeAllComments"; -import { useMutation } from "react-apollo-hooks"; +import { useMutation } from "@apollo/react-hooks"; import { CREATE_COMMENT, RESOLVE_COMMENT, diff --git a/src/components/DiscussionList.tsx b/src/components/DiscussionList.tsx index f7eb831..6d409c0 100644 --- a/src/components/DiscussionList.tsx +++ b/src/components/DiscussionList.tsx @@ -1,6 +1,6 @@ import React, { useState } from "react"; import { useTranslation } from "react-i18next"; -import { useMutation, useSubscription } from "react-apollo-hooks"; +import { useMutation, useSubscription } from "@apollo/react-hooks"; import { getOperationName, DocumentNode } from "apollo-link"; import { useSelector } from "react-redux"; diff --git a/src/pages/Chapter/Chapter.tsx b/src/pages/Chapter/Chapter.tsx index fe29acd..2211774 100644 --- a/src/pages/Chapter/Chapter.tsx +++ b/src/pages/Chapter/Chapter.tsx @@ -1,6 +1,6 @@ import * as React from "react"; import { RouteComponentProps, Redirect } from "react-router-dom"; -import { useSubscription } from "react-apollo-hooks"; +import { useSubscription } from "@apollo/react-hooks"; import { useTranslation } from "react-i18next"; import { withStyles, WithStyles } from "@material-ui/core/styles"; diff --git a/src/pages/Chapter/Chapters.tsx b/src/pages/Chapter/Chapters.tsx index 2897354..8ad0392 100644 --- a/src/pages/Chapter/Chapters.tsx +++ b/src/pages/Chapter/Chapters.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { useQuery } from "react-apollo-hooks"; +import { useQuery } from "@apollo/react-hooks"; import { withStyles, WithStyles } from "@material-ui/core/styles"; import Grid from "@material-ui/core/Grid"; diff --git a/src/pages/Chapter/NewChapter.tsx b/src/pages/Chapter/NewChapter.tsx index 42af5ad..57c6903 100644 --- a/src/pages/Chapter/NewChapter.tsx +++ b/src/pages/Chapter/NewChapter.tsx @@ -3,7 +3,7 @@ import { useTranslation } from "react-i18next"; import * as Yup from "yup"; import { Formik, Form, Field, FormikActions } from "formik"; import { TextField, Select } from "formik-material-ui"; -import { useMutation, useQuery } from "react-apollo-hooks"; +import { useMutation, useQuery } from "@apollo/react-hooks"; import { withStyles, WithStyles } from "@material-ui/core/styles"; import Typography from "@material-ui/core/Typography"; diff --git a/src/pages/Dashboard/ChapterSection.tsx b/src/pages/Dashboard/ChapterSection.tsx index 6015af1..9048081 100644 --- a/src/pages/Dashboard/ChapterSection.tsx +++ b/src/pages/Dashboard/ChapterSection.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { useQuery } from "react-apollo-hooks"; +import { useQuery } from "@apollo/react-hooks"; import { withStyles, WithStyles } from "@material-ui/styles"; import Grid from "@material-ui/core/Grid"; diff --git a/src/pages/Dashboard/VoggiSection.tsx b/src/pages/Dashboard/VoggiSection.tsx index 91d5c54..33c5619 100644 --- a/src/pages/Dashboard/VoggiSection.tsx +++ b/src/pages/Dashboard/VoggiSection.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { useSubscription } from "react-apollo-hooks"; +import { useSubscription } from "@apollo/react-hooks"; import { withStyles, WithStyles } from "@material-ui/styles"; import Grid from "@material-ui/core/Grid"; diff --git a/src/pages/Settings/Settings.tsx b/src/pages/Settings/Settings.tsx index c793aa6..76e14f3 100644 --- a/src/pages/Settings/Settings.tsx +++ b/src/pages/Settings/Settings.tsx @@ -1,7 +1,7 @@ import React from "react"; import { useTranslation } from "react-i18next"; import * as Yup from "yup"; -import { useQuery, useMutation } from "react-apollo-hooks"; +import { useQuery, useMutation } from "@apollo/react-hooks"; import { Form, Formik, FormikActions } from "formik"; import { withStyles, WithStyles } from "@material-ui/styles"; diff --git a/src/pages/SetupWizard/SetupWizard.tsx b/src/pages/SetupWizard/SetupWizard.tsx index 12d54ce..c8573fc 100644 --- a/src/pages/SetupWizard/SetupWizard.tsx +++ b/src/pages/SetupWizard/SetupWizard.tsx @@ -1,7 +1,7 @@ import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { Formik, FormikActions, Form } from "formik"; -import { useMutation } from "react-apollo-hooks"; +import { useMutation } from "@apollo/react-hooks"; import Stepper from "@material-ui/core/Stepper"; import Step from "@material-ui/core/Step"; diff --git a/src/pages/WordGroup/ChapterWordGroups.tsx b/src/pages/WordGroup/ChapterWordGroups.tsx index fd26e81..0f68b1f 100644 --- a/src/pages/WordGroup/ChapterWordGroups.tsx +++ b/src/pages/WordGroup/ChapterWordGroups.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import {useTranslation} from "react-i18next"; import {withStyles, WithStyles} from "@material-ui/core/styles"; -import {useQuery} from "react-apollo-hooks"; +import {useQuery} from "@apollo/react-hooks"; import {RouteComponentProps} from "react-router-dom"; import Grid from "@material-ui/core/Grid"; import AddIcon from "@material-ui/icons/Add"; diff --git a/src/pages/WordGroup/WordEditor.tsx b/src/pages/WordGroup/WordEditor.tsx index a073f83..06e7de6 100644 --- a/src/pages/WordGroup/WordEditor.tsx +++ b/src/pages/WordGroup/WordEditor.tsx @@ -3,7 +3,7 @@ import { useTranslation } from "react-i18next"; import * as Yup from "yup"; import { Formik, Form, Field, FormikActions } from "formik"; import { TextField } from "formik-material-ui"; -import { useMutation } from "react-apollo-hooks"; +import { useMutation } from "@apollo/react-hooks"; import { withStyles, WithStyles } from "@material-ui/core/styles"; import Typography from "@material-ui/core/Typography"; @@ -44,7 +44,7 @@ const defaultValues = { text: "", audio: "", example_sentence: "" }; const WordEditor = ({ classes, match, values = defaultValues }: Props) => { const { t } = useTranslation(); - // TODO: Unfortunately, react-apollo-hooks doesn't support yet the error, loading object in mutations (unlike with query...) + // TODO: Unfortunately, @apollo/react-hooks doesn't support yet the error, loading object in mutations (unlike with query...) const [upsertWord] = useMutation(UPSERT_WORD); async function handleSave(values: any, actions: FormikActions) { diff --git a/src/pages/WordGroup/WordGroup.tsx b/src/pages/WordGroup/WordGroup.tsx index 3a5f2de..51eed3a 100644 --- a/src/pages/WordGroup/WordGroup.tsx +++ b/src/pages/WordGroup/WordGroup.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import { withStyles, WithStyles } from "@material-ui/core/styles"; -import { useQuery } from "react-apollo-hooks"; +import { useQuery } from "@apollo/react-hooks"; import { RouteComponentProps } from "react-router-dom"; import AddIcon from "@material-ui/icons/Add"; diff --git a/src/pages/WordGroup/WordGroupEditor.tsx b/src/pages/WordGroup/WordGroupEditor.tsx index 12cf9d9..399b1d3 100644 --- a/src/pages/WordGroup/WordGroupEditor.tsx +++ b/src/pages/WordGroup/WordGroupEditor.tsx @@ -3,7 +3,7 @@ import { useTranslation } from "react-i18next"; import * as Yup from "yup"; import { Formik, Form, Field, FormikActions } from "formik"; import { TextField } from "formik-material-ui"; -import { useMutation } from "react-apollo-hooks"; +import { useMutation } from "@apollo/react-hooks"; import { withStyles, WithStyles } from "@material-ui/core/styles"; import Typography from "@material-ui/core/Typography"; diff --git a/src/pages/WordGroup/WordGroups.tsx b/src/pages/WordGroup/WordGroups.tsx index b7f0d92..4f6973c 100644 --- a/src/pages/WordGroup/WordGroups.tsx +++ b/src/pages/WordGroup/WordGroups.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { useQuery } from "react-apollo-hooks"; +import { useQuery } from "@apollo/react-hooks"; import { withStyles, WithStyles } from "@material-ui/core/styles"; import Grid from "@material-ui/core/Grid"; From 20bd25c2387a7af2b2e33722f7047297cfc7bfb2 Mon Sep 17 00:00:00 2001 From: David Friederich Date: Thu, 8 Aug 2019 18:25:02 +0200 Subject: [PATCH 092/180] fix merge fails --- package-lock.json | 433 ++++++++++++------ .../ContentEditor/ComponentSelector.tsx | 2 +- 2 files changed, 302 insertions(+), 133 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7b86580..d338be0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1037,6 +1037,14 @@ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.7.2.tgz", "integrity": "sha512-RMtr1i6E8MXaBWwhXL3yeOU8JXRnz8GNxHvaUfVvwxokvayUY0zoBeWbKw1S9XkufmGEEdQd228pSZXFkAln8Q==" }, + "@emotion/is-prop-valid": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.2.tgz", + "integrity": "sha512-ZQIMAA2kLUWiUeMZNJDTeCwYRx1l8SQL0kHktze4COT22occKpDML1GDUXP5/sxhOMrZO8vZw773ni4H5Snrsg==", + "requires": { + "@emotion/memoize": "0.7.2" + } + }, "@emotion/memoize": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.2.tgz", @@ -1768,6 +1776,14 @@ "version": "16.8.5", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.8.5.tgz", "integrity": "sha512-idCEjROZ2cqh29+trmTmZhsBAUNQuYrF92JHKzZ5+aiFM1mlSk3bb23CK7HhYuOY75Apgap5y2jTyHzaM2AJGA==", + "requires": { + "@types/react": "*" + } + }, + "@types/react-native": { + "version": "0.60.3", + "resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.60.3.tgz", + "integrity": "sha512-VjXawSlRG6l620tXB2dzYIwwXfzOZrl4kYHuX09EF0W9QFw/vwH7n7mp+0aFrwlr8ZOkWX/SZFPp6osyRpTGtg==", "requires": { "@types/prop-types": "*", "@types/react": "*" @@ -2716,6 +2732,17 @@ "resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.2.tgz", "integrity": "sha512-CxwvxrZ9OirpXQ201Ec57OmGhmI8/ui/GwTDy0hSp6CmRvgRC0pSair6Z04Ck+JStA0sMPZzSJ3uE4n17EXpPQ==" }, + "babel-plugin-styled-components": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-1.10.6.tgz", + "integrity": "sha512-gyQj/Zf1kQti66100PhrCRjI5ldjaze9O0M3emXRPAN80Zsf8+e1thpTpaXJXVHXtaM4/+dJEgZHyS9Its+8SA==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-module-imports": "^7.0.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "lodash": "^4.17.11" + } + }, "babel-plugin-syntax-jsx": { "version": "6.18.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", @@ -3420,22 +3447,26 @@ "dependencies": { "abbrev": { "version": "1.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "optional": true }, "ansi-regex": { "version": "2.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "optional": true }, "aproba": { "version": "1.2.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "optional": true }, "are-we-there-yet": { "version": "1.1.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "optional": true, "requires": { "delegates": "^1.0.0", @@ -3444,12 +3475,14 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "optional": true }, "brace-expansion": { "version": "1.1.11", - "bundled": true, + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "optional": true, "requires": { "balanced-match": "^1.0.0", @@ -3458,32 +3491,38 @@ }, "chownr": { "version": "1.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", "optional": true }, "code-point-at": { "version": "1.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "optional": true }, "concat-map": { "version": "0.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "optional": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "optional": true }, "core-util-is": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "optional": true }, "debug": { "version": "4.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "optional": true, "requires": { "ms": "^2.1.1" @@ -3491,22 +3530,26 @@ }, "deep-extend": { "version": "0.6.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "optional": true }, "delegates": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "optional": true }, "detect-libc": { "version": "1.0.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "optional": true }, "fs-minipass": { "version": "1.2.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "optional": true, "requires": { "minipass": "^2.2.1" @@ -3514,12 +3557,14 @@ }, "fs.realpath": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "optional": true }, "gauge": { "version": "2.7.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "optional": true, "requires": { "aproba": "^1.0.3", @@ -3534,7 +3579,8 @@ }, "glob": { "version": "7.1.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "optional": true, "requires": { "fs.realpath": "^1.0.0", @@ -3547,12 +3593,14 @@ }, "has-unicode": { "version": "2.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "optional": true }, "iconv-lite": { "version": "0.4.24", - "bundled": true, + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "optional": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" @@ -3560,7 +3608,8 @@ }, "ignore-walk": { "version": "3.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "optional": true, "requires": { "minimatch": "^3.0.4" @@ -3568,7 +3617,8 @@ }, "inflight": { "version": "1.0.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "optional": true, "requires": { "once": "^1.3.0", @@ -3577,17 +3627,20 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "optional": true }, "ini": { "version": "1.3.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "optional": true, "requires": { "number-is-nan": "^1.0.0" @@ -3595,12 +3648,14 @@ }, "isarray": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "optional": true }, "minimatch": { "version": "3.0.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "optional": true, "requires": { "brace-expansion": "^1.1.7" @@ -3608,12 +3663,14 @@ }, "minimist": { "version": "0.0.8", - "bundled": true, + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "optional": true }, "minipass": { "version": "2.3.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", + "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "optional": true, "requires": { "safe-buffer": "^5.1.2", @@ -3622,7 +3679,8 @@ }, "minizlib": { "version": "1.2.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", "optional": true, "requires": { "minipass": "^2.2.1" @@ -3630,7 +3688,8 @@ }, "mkdirp": { "version": "0.5.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "optional": true, "requires": { "minimist": "0.0.8" @@ -3638,12 +3697,14 @@ }, "ms": { "version": "2.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "optional": true }, "needle": { "version": "2.3.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/needle/-/needle-2.3.0.tgz", + "integrity": "sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg==", "optional": true, "requires": { "debug": "^4.1.0", @@ -3653,7 +3714,8 @@ }, "node-pre-gyp": { "version": "0.12.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz", + "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==", "optional": true, "requires": { "detect-libc": "^1.0.2", @@ -3670,7 +3732,8 @@ }, "nopt": { "version": "4.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "optional": true, "requires": { "abbrev": "1", @@ -3679,12 +3742,14 @@ }, "npm-bundled": { "version": "1.0.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz", + "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", "optional": true }, "npm-packlist": { "version": "1.4.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.1.tgz", + "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", "optional": true, "requires": { "ignore-walk": "^3.0.1", @@ -3693,7 +3758,8 @@ }, "npmlog": { "version": "4.1.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "optional": true, "requires": { "are-we-there-yet": "~1.1.2", @@ -3704,17 +3770,20 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "optional": true }, "object-assign": { "version": "4.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "optional": true }, "once": { "version": "1.4.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "optional": true, "requires": { "wrappy": "1" @@ -3722,17 +3791,20 @@ }, "os-homedir": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "optional": true }, "os-tmpdir": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "optional": true }, "osenv": { "version": "0.1.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "optional": true, "requires": { "os-homedir": "^1.0.0", @@ -3741,17 +3813,20 @@ }, "path-is-absolute": { "version": "1.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "optional": true }, "process-nextick-args": { "version": "2.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "optional": true }, "rc": { "version": "1.2.8", - "bundled": true, + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "optional": true, "requires": { "deep-extend": "^0.6.0", @@ -3762,14 +3837,16 @@ "dependencies": { "minimist": { "version": "1.2.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "optional": true } } }, "readable-stream": { "version": "2.3.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "optional": true, "requires": { "core-util-is": "~1.0.0", @@ -3783,7 +3860,8 @@ }, "rimraf": { "version": "2.6.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "optional": true, "requires": { "glob": "^7.1.3" @@ -3791,37 +3869,44 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "optional": true }, "safer-buffer": { "version": "2.1.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "optional": true }, "sax": { "version": "1.2.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "optional": true }, "semver": { "version": "5.7.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", "optional": true }, "set-blocking": { "version": "2.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "optional": true }, "signal-exit": { "version": "3.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "optional": true }, "string-width": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "optional": true, "requires": { "code-point-at": "^1.0.0", @@ -3831,7 +3916,8 @@ }, "string_decoder": { "version": "1.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "optional": true, "requires": { "safe-buffer": "~5.1.0" @@ -3839,7 +3925,8 @@ }, "strip-ansi": { "version": "3.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "optional": true, "requires": { "ansi-regex": "^2.0.0" @@ -3847,12 +3934,14 @@ }, "strip-json-comments": { "version": "2.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "optional": true }, "tar": { "version": "4.4.8", - "bundled": true, + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz", + "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", "optional": true, "requires": { "chownr": "^1.1.1", @@ -3866,12 +3955,14 @@ }, "util-deprecate": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "optional": true }, "wide-align": { "version": "1.1.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "optional": true, "requires": { "string-width": "^1.0.2 || 2" @@ -3879,12 +3970,14 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "optional": true }, "yallist": { "version": "3.0.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", "optional": true } } @@ -7622,22 +7715,26 @@ "dependencies": { "abbrev": { "version": "1.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "optional": true }, "ansi-regex": { "version": "2.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "optional": true }, "aproba": { "version": "1.2.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "optional": true }, "are-we-there-yet": { "version": "1.1.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "optional": true, "requires": { "delegates": "^1.0.0", @@ -7646,12 +7743,14 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "optional": true }, "brace-expansion": { "version": "1.1.11", - "bundled": true, + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "optional": true, "requires": { "balanced-match": "^1.0.0", @@ -7660,32 +7759,38 @@ }, "chownr": { "version": "1.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", "optional": true }, "code-point-at": { "version": "1.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "optional": true }, "concat-map": { "version": "0.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "optional": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "optional": true }, "core-util-is": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "optional": true }, "debug": { "version": "4.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "optional": true, "requires": { "ms": "^2.1.1" @@ -7693,22 +7798,26 @@ }, "deep-extend": { "version": "0.6.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "optional": true }, "delegates": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "optional": true }, "detect-libc": { "version": "1.0.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "optional": true }, "fs-minipass": { "version": "1.2.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "optional": true, "requires": { "minipass": "^2.2.1" @@ -7716,12 +7825,14 @@ }, "fs.realpath": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "optional": true }, "gauge": { "version": "2.7.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "optional": true, "requires": { "aproba": "^1.0.3", @@ -7736,7 +7847,8 @@ }, "glob": { "version": "7.1.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "optional": true, "requires": { "fs.realpath": "^1.0.0", @@ -7749,12 +7861,14 @@ }, "has-unicode": { "version": "2.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "optional": true }, "iconv-lite": { "version": "0.4.24", - "bundled": true, + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "optional": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" @@ -7762,7 +7876,8 @@ }, "ignore-walk": { "version": "3.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "optional": true, "requires": { "minimatch": "^3.0.4" @@ -7770,7 +7885,8 @@ }, "inflight": { "version": "1.0.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "optional": true, "requires": { "once": "^1.3.0", @@ -7779,17 +7895,20 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "optional": true }, "ini": { "version": "1.3.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "optional": true, "requires": { "number-is-nan": "^1.0.0" @@ -7797,12 +7916,14 @@ }, "isarray": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "optional": true }, "minimatch": { "version": "3.0.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "optional": true, "requires": { "brace-expansion": "^1.1.7" @@ -7810,12 +7931,14 @@ }, "minimist": { "version": "0.0.8", - "bundled": true, + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "optional": true }, "minipass": { "version": "2.3.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", + "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "optional": true, "requires": { "safe-buffer": "^5.1.2", @@ -7824,7 +7947,8 @@ }, "minizlib": { "version": "1.2.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", "optional": true, "requires": { "minipass": "^2.2.1" @@ -7832,7 +7956,8 @@ }, "mkdirp": { "version": "0.5.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "optional": true, "requires": { "minimist": "0.0.8" @@ -7840,12 +7965,14 @@ }, "ms": { "version": "2.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "optional": true }, "needle": { "version": "2.3.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/needle/-/needle-2.3.0.tgz", + "integrity": "sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg==", "optional": true, "requires": { "debug": "^4.1.0", @@ -7855,7 +7982,8 @@ }, "node-pre-gyp": { "version": "0.12.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz", + "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==", "optional": true, "requires": { "detect-libc": "^1.0.2", @@ -7872,7 +8000,8 @@ }, "nopt": { "version": "4.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "optional": true, "requires": { "abbrev": "1", @@ -7881,12 +8010,14 @@ }, "npm-bundled": { "version": "1.0.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz", + "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", "optional": true }, "npm-packlist": { "version": "1.4.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.1.tgz", + "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", "optional": true, "requires": { "ignore-walk": "^3.0.1", @@ -7895,7 +8026,8 @@ }, "npmlog": { "version": "4.1.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "optional": true, "requires": { "are-we-there-yet": "~1.1.2", @@ -7906,17 +8038,20 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "optional": true }, "object-assign": { "version": "4.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "optional": true }, "once": { "version": "1.4.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "optional": true, "requires": { "wrappy": "1" @@ -7924,17 +8059,20 @@ }, "os-homedir": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "optional": true }, "os-tmpdir": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "optional": true }, "osenv": { "version": "0.1.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "optional": true, "requires": { "os-homedir": "^1.0.0", @@ -7943,17 +8081,20 @@ }, "path-is-absolute": { "version": "1.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "optional": true }, "process-nextick-args": { "version": "2.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "optional": true }, "rc": { "version": "1.2.8", - "bundled": true, + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "optional": true, "requires": { "deep-extend": "^0.6.0", @@ -7964,14 +8105,16 @@ "dependencies": { "minimist": { "version": "1.2.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "optional": true } } }, "readable-stream": { "version": "2.3.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "optional": true, "requires": { "core-util-is": "~1.0.0", @@ -7985,7 +8128,8 @@ }, "rimraf": { "version": "2.6.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "optional": true, "requires": { "glob": "^7.1.3" @@ -7993,37 +8137,44 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "optional": true }, "safer-buffer": { "version": "2.1.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "optional": true }, "sax": { "version": "1.2.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "optional": true }, "semver": { "version": "5.7.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", "optional": true }, "set-blocking": { "version": "2.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "optional": true }, "signal-exit": { "version": "3.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "optional": true }, "string-width": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "optional": true, "requires": { "code-point-at": "^1.0.0", @@ -8033,7 +8184,8 @@ }, "string_decoder": { "version": "1.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "optional": true, "requires": { "safe-buffer": "~5.1.0" @@ -8041,7 +8193,8 @@ }, "strip-ansi": { "version": "3.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "optional": true, "requires": { "ansi-regex": "^2.0.0" @@ -8049,12 +8202,14 @@ }, "strip-json-comments": { "version": "2.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "optional": true }, "tar": { "version": "4.4.8", - "bundled": true, + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz", + "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", "optional": true, "requires": { "chownr": "^1.1.1", @@ -8068,12 +8223,14 @@ }, "util-deprecate": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "optional": true }, "wide-align": { "version": "1.1.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "optional": true, "requires": { "string-width": "^1.0.2 || 2" @@ -8081,12 +8238,14 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "optional": true }, "yallist": { "version": "3.0.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", "optional": true } } @@ -12948,6 +13107,16 @@ } } }, + "stylis": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-3.5.4.tgz", + "integrity": "sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q==" + }, + "stylis-rule-sheet": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz", + "integrity": "sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw==" + }, "subscriptions-transport-ws": { "version": "0.9.16", "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.16.tgz", diff --git a/src/components/ContentEditor/ComponentSelector.tsx b/src/components/ContentEditor/ComponentSelector.tsx index be94ccf..90dd000 100644 --- a/src/components/ContentEditor/ComponentSelector.tsx +++ b/src/components/ContentEditor/ComponentSelector.tsx @@ -5,7 +5,7 @@ import { DraggableProvided, DraggableStateSnapshot } from "react-beautiful-dnd"; -import { useQuery } from "react-apollo-hooks"; +import { useQuery } from "@apollo/react-hooks"; import classNames from "classnames"; import styled from "styled-components"; From ab277af447eabd4943ef0ea6ec7cd5abbb442a18 Mon Sep 17 00:00:00 2001 From: David Friederich Date: Fri, 9 Aug 2019 17:01:17 +0200 Subject: [PATCH 093/180] Adds creating (without content), reordering and deletion of components --- src/__generated__/globalTypes.ts | 70 +++++++++------ .../ContentEditor/ContentEditor.tsx | 42 +++++++++ .../components/BaseComponent.tsx | 14 +-- .../components/ComponentHeader.tsx | 85 +++++++++++++++++++ src/queries/__generated__/createComponent.ts | 33 +++++++ src/queries/__generated__/deleteComponent.ts | 31 +++++++ src/queries/__generated__/updateComponent.ts | 35 ++++++++ src/queries/component.ts | 32 +++++++ 8 files changed, 303 insertions(+), 39 deletions(-) create mode 100644 src/components/ContentEditor/components/ComponentHeader.tsx create mode 100644 src/queries/__generated__/createComponent.ts create mode 100644 src/queries/__generated__/deleteComponent.ts create mode 100644 src/queries/__generated__/updateComponent.ts create mode 100644 src/queries/component.ts diff --git a/src/__generated__/globalTypes.ts b/src/__generated__/globalTypes.ts index 515a6f2..c39b4a1 100644 --- a/src/__generated__/globalTypes.ts +++ b/src/__generated__/globalTypes.ts @@ -11,7 +11,7 @@ */ export enum ProfileLanguage { DE = "DE", - EN = "EN" + EN = "EN", } /** @@ -21,7 +21,7 @@ export enum api_chapter_constraint { api_chapter_pkey = "api_chapter_pkey", api_chapter_titleCH_key = "api_chapter_titleCH_key", api_chapter_titleDE_key = "api_chapter_titleDE_key", - api_chapter_titleDE_number_ffcac7a1_uniq = "api_chapter_titleDE_number_ffcac7a1_uniq" + api_chapter_titleDE_number_ffcac7a1_uniq = "api_chapter_titleDE_number_ffcac7a1_uniq", } /** @@ -29,7 +29,7 @@ export enum api_chapter_constraint { */ export enum api_chapter_languages_constraint { api_chapter_languages_chapter_id_language_id_27c00225_uniq = "api_chapter_languages_chapter_id_language_id_27c00225_uniq", - api_chapter_languages_pkey = "api_chapter_languages_pkey" + api_chapter_languages_pkey = "api_chapter_languages_pkey", } /** @@ -38,7 +38,7 @@ export enum api_chapter_languages_constraint { export enum api_chapter_languages_update_column { chapter_id = "chapter_id", id = "id", - language_id = "language_id" + language_id = "language_id", } /** @@ -52,14 +52,14 @@ export enum api_chapter_update_column { number = "number", titleCH = "titleCH", titleDE = "titleDE", - updated = "updated" + updated = "updated", } /** * unique or primary key constraints on table "api_comment" */ export enum api_comment_constraint { - api_comment_pkey = "api_comment_pkey" + api_comment_pkey = "api_comment_pkey", } /** @@ -75,7 +75,7 @@ export enum api_comment_update_column { id = "id", text = "text", updated = "updated", - written = "written" + written = "written", } /** @@ -83,7 +83,6 @@ export enum api_comment_update_column { */ export enum api_component_constraint { api_component_pkey = "api_component_pkey", - ordered_list_null = "ordered_list_null" } /** @@ -100,14 +99,14 @@ export enum api_component_update_column { locked_ts = "locked_ts", order_in_chapter = "order_in_chapter", state = "state", - updated = "updated" + updated = "updated", } /** * unique or primary key constraints on table "api_componenttype" */ export enum api_componenttype_constraint { - api_componenttype_pkey = "api_componenttype_pkey" + api_componenttype_pkey = "api_componenttype_pkey", } /** @@ -122,14 +121,14 @@ export enum api_componenttype_update_column { label = "label", name = "name", schema = "schema", - updated = "updated" + updated = "updated", } /** * unique or primary key constraints on table "api_language" */ export enum api_language_constraint { - api_language_pkey = "api_language_pkey" + api_language_pkey = "api_language_pkey", } /** @@ -140,7 +139,7 @@ export enum api_language_update_column { created = "created", id = "id", name = "name", - updated = "updated" + updated = "updated", } /** @@ -148,7 +147,7 @@ export enum api_language_update_column { */ export enum api_profile_constraint { api_profile_pkey = "api_profile_pkey", - api_profile_user_id_key = "api_profile_user_id_key" + api_profile_user_id_key = "api_profile_user_id_key", } /** @@ -166,7 +165,7 @@ export enum api_profile_update_column { setup_completed = "setup_completed", translator_languages = "translator_languages", updated = "updated", - user_id = "user_id" + user_id = "user_id", } /** @@ -174,7 +173,7 @@ export enum api_profile_update_column { */ export enum api_text_constraint { api_text_master_translation_id_key = "api_text_master_translation_id_key", - api_text_pkey = "api_text_pkey" + api_text_pkey = "api_text_pkey", } /** @@ -186,7 +185,7 @@ export enum api_text_update_column { id = "id", master_translation_id = "master_translation_id", translatable = "translatable", - updated = "updated" + updated = "updated", } /** @@ -194,7 +193,7 @@ export enum api_text_update_column { */ export enum api_translation_constraint { api_translation_fk_language_id_fk_text_id_807318e5_uniq = "api_translation_fk_language_id_fk_text_id_807318e5_uniq", - api_translation_pkey = "api_translation_pkey" + api_translation_pkey = "api_translation_pkey", } /** @@ -207,14 +206,14 @@ export enum api_translation_update_column { id = "id", text_field = "text_field", updated = "updated", - valid = "valid" + valid = "valid", } /** * unique or primary key constraints on table "api_word" */ export enum api_word_constraint { - api_word_pkey = "api_word_pkey" + api_word_pkey = "api_word_pkey", } /** @@ -223,14 +222,14 @@ export enum api_word_constraint { export enum api_word_update_column { created = "created", id = "id", - updated = "updated" + updated = "updated", } /** * unique or primary key constraints on table "api_wordgroup" */ export enum api_wordgroup_constraint { - api_wordgroup_pkey = "api_wordgroup_pkey" + api_wordgroup_pkey = "api_wordgroup_pkey", } /** @@ -242,7 +241,7 @@ export enum api_wordgroup_update_column { id = "id", title_ch = "title_ch", title_de = "title_de", - updated = "updated" + updated = "updated", } /** @@ -250,7 +249,7 @@ export enum api_wordgroup_update_column { */ export enum api_wordgroup_words_constraint { api_wordgroup_words_pkey = "api_wordgroup_words_pkey", - api_wordgroup_words_wordgroup_id_word_id_aebfaecb_uniq = "api_wordgroup_words_wordgroup_id_word_id_aebfaecb_uniq" + api_wordgroup_words_wordgroup_id_word_id_aebfaecb_uniq = "api_wordgroup_words_wordgroup_id_word_id_aebfaecb_uniq", } /** @@ -259,14 +258,14 @@ export enum api_wordgroup_words_constraint { export enum api_wordgroup_words_update_column { id = "id", word_id = "word_id", - wordgroup_id = "wordgroup_id" + wordgroup_id = "wordgroup_id", } /** * unique or primary key constraints on table "api_wordtranslation" */ export enum api_wordtranslation_constraint { - api_wordtranslation_pkey = "api_wordtranslation_pkey" + api_wordtranslation_pkey = "api_wordtranslation_pkey", } /** @@ -280,7 +279,7 @@ export enum api_wordtranslation_update_column { id = "id", text = "text", updated = "updated", - word_id = "word_id" + word_id = "word_id", } export interface ProfileInput { @@ -461,6 +460,23 @@ export interface api_component_on_conflict { update_columns: api_component_update_column[]; } +/** + * input type for updating data in table "api_component" + */ +export interface api_component_set_input { + created?: any | null; + data?: string | null; + fk_chapter_id?: any | null; + fk_component_id?: any | null; + fk_component_type_id?: any | null; + fk_locked_by_id?: any | null; + id?: any | null; + locked_ts?: any | null; + order_in_chapter?: number | null; + state?: string | null; + updated?: any | null; +} + /** * input type for inserting array relation for remote table "api_componenttype" */ diff --git a/src/components/ContentEditor/ContentEditor.tsx b/src/components/ContentEditor/ContentEditor.tsx index 82ebb94..6f43c2a 100644 --- a/src/components/ContentEditor/ContentEditor.tsx +++ b/src/components/ContentEditor/ContentEditor.tsx @@ -1,6 +1,7 @@ import * as React from "react"; import { DragDropContext, Droppable, DropResult } from "react-beautiful-dnd"; import classNames from "classnames"; +import { useMutation } from "@apollo/react-hooks"; import { makeStyles, Theme, Grid } from "@material-ui/core"; @@ -10,6 +11,7 @@ import { subscribeChapterById_chapter } from "queries/__generated__/subscribeCha import { useSelector } from "react-redux"; import { TAppState } from "reducers"; import { IContentEditorState } from "reducers/contentEditorSlice"; +import { CREATE_COMPONENT, UPDATE_COMPONENT } from "queries/component"; export const TOP_LEVEL_COMPONENT_TYPE = "top-level-component"; @@ -30,6 +32,16 @@ const ContentEditor = ({ data }: Props) => { state => state.contentEditor ); + const [ + createComponent, + { loading: createLoading, error: createError } + ] = useMutation(CREATE_COMPONENT); + + const [ + updateComponent, + { loading: updateLoading, error: updateError } + ] = useMutation(UPDATE_COMPONENT); + /** * Is called when the drag ends. Main function that handles all the logic related to DragAndDrop */ @@ -43,6 +55,36 @@ const ContentEditor = ({ data }: Props) => { if (result.destination.index === result.source.index) { return; } + + // INSERT: When the source is the component-selector-, then its actually a creation of a new component + if (result.source.droppableId.startsWith("component-selector-")) { + createComponent({ + variables: { + input: { + fk_chapter_id: data.id, + fk_component_id: + (selectedComponent && selectedComponent.id) || null, + fk_component_type_id: result.draggableId, + data: "ABCD", + order_in_chapter: result.destination.index + 1, + state: "C", + locked_ts: new Date() + } + } + }); + } + + // UPDATE: When the source is within the component list (any level), then it must be an update of a component + else if (result.source.droppableId.startsWith("component-list-")) { + updateComponent({ + variables: { + id: result.draggableId, + data: { + order_in_chapter: result.destination.index + 1 + } + } + }); + } } return ( diff --git a/src/components/ContentEditor/components/BaseComponent.tsx b/src/components/ContentEditor/components/BaseComponent.tsx index f4715ac..69737a1 100644 --- a/src/components/ContentEditor/components/BaseComponent.tsx +++ b/src/components/ContentEditor/components/BaseComponent.tsx @@ -12,6 +12,7 @@ import { actions, IContentEditorState } from "reducers/contentEditorSlice"; import { subscribeChapterById_chapter_components } from "queries/__generated__/subscribeChapterById"; import ComponentList from "../ComponentList"; import { TAppState } from "reducers"; +import ComponentHeader from "./ComponentHeader"; const useStyles = makeStyles((theme: Theme) => ({ container: { @@ -86,18 +87,7 @@ const BaseComponent = ({ level, index, data }: Props) => { )} onClick={handleOnComponentClick} > - - - - - {data.type.icon} - {data.data} - {isSelected ? ( - - - - ) : null} - + {data.children.length ? ( // diff --git a/src/components/ContentEditor/components/ComponentHeader.tsx b/src/components/ContentEditor/components/ComponentHeader.tsx new file mode 100644 index 0000000..8a1c985 --- /dev/null +++ b/src/components/ContentEditor/components/ComponentHeader.tsx @@ -0,0 +1,85 @@ +import * as React from "react"; +import classNames from "classnames"; +import { useMutation } from "@apollo/react-hooks"; + +import { makeStyles } from "@material-ui/styles"; +import { Theme } from "@material-ui/core/styles"; +import { + Icon, + Typography, + Grid, + IconButton, + Menu, + MenuItem +} from "@material-ui/core"; + +import { subscribeChapterById_chapter_components } from "queries/__generated__/subscribeChapterById"; +import { DragHandle, MoreVert } from "@material-ui/icons"; +import { DELETE_COMPONENT } from "queries/component"; + +const useStyles = makeStyles((theme: Theme) => ({ + container: { + border: "solid", + borderWidth: 2, + borderColor: theme.palette.grey[200], + marginBottom: theme.spacing(2) + } +})); + +interface Props { + provided: any; + data: subscribeChapterById_chapter_components; +} + +const ComponentHeader = ({ provided, data }: Props) => { + const classes = useStyles(); + const [anchorEl, setAnchorEl] = React.useState(null); + const open = Boolean(anchorEl); + + const [ + deleteComponent, + { loading: deleteLoading, error: deleteError } + ] = useMutation(DELETE_COMPONENT); + + function handleClick(event: React.MouseEvent) { + setAnchorEl(event.currentTarget); + } + + function handleClose() { + setAnchorEl(null); + } + + function handleDelete() { + deleteComponent({ variables: { id: data.id } }); + } + + return ( + + + + + {data.type.icon} + {data.data} + + + + + Delete + ))} + + + ); +}; + +export default ComponentHeader; diff --git a/src/queries/__generated__/createComponent.ts b/src/queries/__generated__/createComponent.ts new file mode 100644 index 0000000..9cb72f7 --- /dev/null +++ b/src/queries/__generated__/createComponent.ts @@ -0,0 +1,33 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +import { api_component_insert_input } from "./../../__generated__/globalTypes"; + +// ==================================================== +// GraphQL mutation operation: createComponent +// ==================================================== + +export interface createComponent_insert_api_component_returning { + __typename: "api_component"; + id: any; +} + +export interface createComponent_insert_api_component { + __typename: "api_component_mutation_response"; + /** + * data of the affected rows by the mutation + */ + returning: createComponent_insert_api_component_returning[]; +} + +export interface createComponent { + /** + * insert data into the table: "api_component" + */ + insert_api_component: createComponent_insert_api_component | null; +} + +export interface createComponentVariables { + input: api_component_insert_input; +} diff --git a/src/queries/__generated__/deleteComponent.ts b/src/queries/__generated__/deleteComponent.ts new file mode 100644 index 0000000..85d3693 --- /dev/null +++ b/src/queries/__generated__/deleteComponent.ts @@ -0,0 +1,31 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +// ==================================================== +// GraphQL mutation operation: deleteComponent +// ==================================================== + +export interface deleteComponent_delete_api_component_returning { + __typename: "api_component"; + id: any; +} + +export interface deleteComponent_delete_api_component { + __typename: "api_component_mutation_response"; + /** + * data of the affected rows by the mutation + */ + returning: deleteComponent_delete_api_component_returning[]; +} + +export interface deleteComponent { + /** + * delete data from the table: "api_component" + */ + delete_api_component: deleteComponent_delete_api_component | null; +} + +export interface deleteComponentVariables { + id: any; +} diff --git a/src/queries/__generated__/updateComponent.ts b/src/queries/__generated__/updateComponent.ts new file mode 100644 index 0000000..d9b0069 --- /dev/null +++ b/src/queries/__generated__/updateComponent.ts @@ -0,0 +1,35 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +import { api_component_set_input } from "./../../__generated__/globalTypes"; + +// ==================================================== +// GraphQL mutation operation: updateComponent +// ==================================================== + +export interface updateComponent_update_api_component_returning { + __typename: "api_component"; + id: any; + order_in_chapter: number | null; +} + +export interface updateComponent_update_api_component { + __typename: "api_component_mutation_response"; + /** + * data of the affected rows by the mutation + */ + returning: updateComponent_update_api_component_returning[]; +} + +export interface updateComponent { + /** + * update data of the table: "api_component" + */ + update_api_component: updateComponent_update_api_component | null; +} + +export interface updateComponentVariables { + id: any; + data: api_component_set_input; +} diff --git a/src/queries/component.ts b/src/queries/component.ts new file mode 100644 index 0000000..3db752e --- /dev/null +++ b/src/queries/component.ts @@ -0,0 +1,32 @@ +import gql from "graphql-tag"; + +export const CREATE_COMPONENT = gql` + mutation createComponent($input: api_component_insert_input!) { + insert_api_component(objects: [$input]) { + returning { + id + } + } + } +`; + +export const UPDATE_COMPONENT = gql` + mutation updateComponent($id: uuid!, $data: api_component_set_input!) { + update_api_component(_set: $data, where: { id: { _eq: $id } }) { + returning { + id + order_in_chapter + } + } + } +`; + +export const DELETE_COMPONENT = gql` + mutation deleteComponent($id: uuid!) { + delete_api_component(where: { id: { _eq: $id } }) { + returning { + id + } + } + } +`; From 9234954c128f600fde7646143c82348cd0c76484 Mon Sep 17 00:00:00 2001 From: David Friederich Date: Mon, 12 Aug 2019 10:30:55 +0200 Subject: [PATCH 094/180] insert empty string to data --- src/components/ContentEditor/ContentEditor.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ContentEditor/ContentEditor.tsx b/src/components/ContentEditor/ContentEditor.tsx index 6f43c2a..e85e684 100644 --- a/src/components/ContentEditor/ContentEditor.tsx +++ b/src/components/ContentEditor/ContentEditor.tsx @@ -65,7 +65,7 @@ const ContentEditor = ({ data }: Props) => { fk_component_id: (selectedComponent && selectedComponent.id) || null, fk_component_type_id: result.draggableId, - data: "ABCD", + data: "", order_in_chapter: result.destination.index + 1, state: "C", locked_ts: new Date() From d87438bf26a81624c5661db822f9798c9ccd527e Mon Sep 17 00:00:00 2001 From: David Friederich Date: Mon, 12 Aug 2019 11:02:12 +0200 Subject: [PATCH 095/180] Stop events from bubbling up, unselect on delete and clickaway listener --- .../components/BaseComponent.tsx | 90 ++++++++++--------- .../components/ComponentHeader.tsx | 21 ++++- 2 files changed, 66 insertions(+), 45 deletions(-) diff --git a/src/components/ContentEditor/components/BaseComponent.tsx b/src/components/ContentEditor/components/BaseComponent.tsx index 69737a1..c8e3f5c 100644 --- a/src/components/ContentEditor/components/BaseComponent.tsx +++ b/src/components/ContentEditor/components/BaseComponent.tsx @@ -1,13 +1,12 @@ import * as React from "react"; import classNames from "classnames"; +import { Draggable, Droppable } from "react-beautiful-dnd"; +import { useDispatch, useSelector } from "react-redux"; import { makeStyles } from "@material-ui/styles"; import { Theme } from "@material-ui/core/styles"; -import { DragHandle, Clear } from "@material-ui/icons"; -import { IconButton, Icon, Typography, Grid } from "@material-ui/core"; +import { Grid, ClickAwayListener } from "@material-ui/core"; -import { Draggable, Droppable } from "react-beautiful-dnd"; -import { useDispatch, useSelector } from "react-redux"; import { actions, IContentEditorState } from "reducers/contentEditorSlice"; import { subscribeChapterById_chapter_components } from "queries/__generated__/subscribeChapterById"; import ComponentList from "../ComponentList"; @@ -58,10 +57,12 @@ const BaseComponent = ({ level, index, data }: Props) => { event: React.MouseEvent ) => { event.stopPropagation(); + event.preventDefault(); selectComponent(); + // handle }; - const handleOnComponentUnselectClick = (event: any) => { + const handleClickAway = (event: any) => { event.stopPropagation(); event.preventDefault(); unselectComponent(); @@ -74,44 +75,49 @@ const BaseComponent = ({ level, index, data }: Props) => { disableInteractiveElementBlocking > {(provided, snapshot) => ( - - + + + - {data.children.length ? ( - // - - {(provided, snapshot) => ( - - - {provided.placeholder} - - )} - - ) : // - null} - + {data.children.length ? ( + // + + {(provided, snapshot) => ( + + + {provided.placeholder} + + )} + + ) : // + null} + + )} ); diff --git a/src/components/ContentEditor/components/ComponentHeader.tsx b/src/components/ContentEditor/components/ComponentHeader.tsx index 8a1c985..8b86119 100644 --- a/src/components/ContentEditor/components/ComponentHeader.tsx +++ b/src/components/ContentEditor/components/ComponentHeader.tsx @@ -1,6 +1,7 @@ import * as React from "react"; import classNames from "classnames"; import { useMutation } from "@apollo/react-hooks"; +import { useDispatch } from "react-redux"; import { makeStyles } from "@material-ui/styles"; import { Theme } from "@material-ui/core/styles"; @@ -12,10 +13,11 @@ import { Menu, MenuItem } from "@material-ui/core"; +import { DragHandle, MoreVert } from "@material-ui/icons"; import { subscribeChapterById_chapter_components } from "queries/__generated__/subscribeChapterById"; -import { DragHandle, MoreVert } from "@material-ui/icons"; import { DELETE_COMPONENT } from "queries/component"; +import { actions, IContentEditorState } from "reducers/contentEditorSlice"; const useStyles = makeStyles((theme: Theme) => ({ container: { @@ -35,6 +37,12 @@ const ComponentHeader = ({ provided, data }: Props) => { const classes = useStyles(); const [anchorEl, setAnchorEl] = React.useState(null); const open = Boolean(anchorEl); + const dispatch = useDispatch(); + + const unselectComponent = React.useCallback( + () => dispatch(actions.setSelectedComponent()), + [dispatch] + ); const [ deleteComponent, @@ -42,15 +50,22 @@ const ComponentHeader = ({ provided, data }: Props) => { ] = useMutation(DELETE_COMPONENT); function handleClick(event: React.MouseEvent) { + event.stopPropagation(); + event.preventDefault(); setAnchorEl(event.currentTarget); } - function handleClose() { + function handleClose(event: any) { + event.stopPropagation(); + event.preventDefault(); setAnchorEl(null); } - function handleDelete() { + function handleDelete(event: any) { + event.stopPropagation(); + event.preventDefault(); deleteComponent({ variables: { id: data.id } }); + unselectComponent(); } return ( From 105f177eb5daf7c35c6eaa5d0512ac75fef18e45 Mon Sep 17 00:00:00 2001 From: David Friederich Date: Mon, 12 Aug 2019 11:02:32 +0200 Subject: [PATCH 096/180] cleanup --- src/components/ContentEditor/components/BaseComponent.tsx | 1 - src/components/ContentEditor/components/ComponentHeader.tsx | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/ContentEditor/components/BaseComponent.tsx b/src/components/ContentEditor/components/BaseComponent.tsx index c8e3f5c..8521026 100644 --- a/src/components/ContentEditor/components/BaseComponent.tsx +++ b/src/components/ContentEditor/components/BaseComponent.tsx @@ -59,7 +59,6 @@ const BaseComponent = ({ level, index, data }: Props) => { event.stopPropagation(); event.preventDefault(); selectComponent(); - // handle }; const handleClickAway = (event: any) => { diff --git a/src/components/ContentEditor/components/ComponentHeader.tsx b/src/components/ContentEditor/components/ComponentHeader.tsx index 8b86119..54bfb8f 100644 --- a/src/components/ContentEditor/components/ComponentHeader.tsx +++ b/src/components/ContentEditor/components/ComponentHeader.tsx @@ -17,7 +17,7 @@ import { DragHandle, MoreVert } from "@material-ui/icons"; import { subscribeChapterById_chapter_components } from "queries/__generated__/subscribeChapterById"; import { DELETE_COMPONENT } from "queries/component"; -import { actions, IContentEditorState } from "reducers/contentEditorSlice"; +import { actions } from "reducers/contentEditorSlice"; const useStyles = makeStyles((theme: Theme) => ({ container: { From 375181b6fffcc24b8a3eedd8fbf2cd27c388525a Mon Sep 17 00:00:00 2001 From: David Friederich Date: Mon, 12 Aug 2019 11:21:30 +0200 Subject: [PATCH 097/180] Add event types --- src/components/ContentEditor/components/BaseComponent.tsx | 2 +- src/components/ContentEditor/components/ComponentHeader.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/ContentEditor/components/BaseComponent.tsx b/src/components/ContentEditor/components/BaseComponent.tsx index 8521026..671dec1 100644 --- a/src/components/ContentEditor/components/BaseComponent.tsx +++ b/src/components/ContentEditor/components/BaseComponent.tsx @@ -61,7 +61,7 @@ const BaseComponent = ({ level, index, data }: Props) => { selectComponent(); }; - const handleClickAway = (event: any) => { + const handleClickAway = (event: React.MouseEvent) => { event.stopPropagation(); event.preventDefault(); unselectComponent(); diff --git a/src/components/ContentEditor/components/ComponentHeader.tsx b/src/components/ContentEditor/components/ComponentHeader.tsx index 54bfb8f..de7b412 100644 --- a/src/components/ContentEditor/components/ComponentHeader.tsx +++ b/src/components/ContentEditor/components/ComponentHeader.tsx @@ -55,13 +55,13 @@ const ComponentHeader = ({ provided, data }: Props) => { setAnchorEl(event.currentTarget); } - function handleClose(event: any) { + function handleClose(event: React.MouseEvent) { event.stopPropagation(); event.preventDefault(); setAnchorEl(null); } - function handleDelete(event: any) { + function handleDelete(event: React.MouseEvent) { event.stopPropagation(); event.preventDefault(); deleteComponent({ variables: { id: data.id } }); From 5b4b18883b05489d44fe23d5ba91d8579707a50e Mon Sep 17 00:00:00 2001 From: David Friederich Date: Mon, 12 Aug 2019 11:54:54 +0200 Subject: [PATCH 098/180] Add "Text" component for translation behavior reusability --- package-lock.json | 5 +++++ package.json | 1 + src/components/Text.tsx | 46 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+) create mode 100644 src/components/Text.tsx diff --git a/package-lock.json b/package-lock.json index d338be0..5888e34 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11352,6 +11352,11 @@ "use-memo-one": "^1.1.0" } }, + "react-children-utilities": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/react-children-utilities/-/react-children-utilities-1.2.2.tgz", + "integrity": "sha512-Yfgx1TqrYmnbMVXV/W6QqL5v53IlffErWdJE81vyfTe3KkRefhmEWl45+5ZImIFHp3UaS06HYVwjI7tf6CNi0g==" + }, "react-dev-utils": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-9.0.1.tgz", diff --git a/package.json b/package.json index 97e33c4..e5a6018 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "i18next-browser-languagedetector": "^3.0.1", "react": "^16.8.6", "react-beautiful-dnd": "^11.0.5", + "react-children-utilities": "^1.2.2", "react-dom": "^16.8.6", "react-i18next": "^10.11.4", "react-router-dom": "^5.0.1", diff --git a/src/components/Text.tsx b/src/components/Text.tsx new file mode 100644 index 0000000..496cfab --- /dev/null +++ b/src/components/Text.tsx @@ -0,0 +1,46 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import Children from "react-children-utilities"; + +import { createStyles, makeStyles, Theme } from "@material-ui/core/styles"; +import Typography, { TypographyProps } from "@material-ui/core/Typography"; + +// Note: This type is not exported by default. With this, we can actually import and use it! +type TOptions = import("i18next").default.TOptions; + +interface IText extends TypographyProps { + /** + * If set as false, then the content (children) passed along will not be attempted to be translated! + */ + translate?: boolean; + translationOptions?: TOptions; +} + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + text: { + marginRight: theme.spacing(2) + } + }) +); + +const Text: React.FC = ({ + children, + className, + translate = true, + translationOptions = {}, + ...otherProps +}) => { + const { t } = useTranslation(); + const classes = useStyles(); + + const text = Children.onlyText(children); + + return ( + + {translate ? t(text, translationOptions) : text} + + ); +}; + +export default Text; From 2752a8fcdb65d4f146f1d304c6c30a05271f8b6f Mon Sep 17 00:00:00 2001 From: David Friederich Date: Mon, 12 Aug 2019 15:59:33 +0200 Subject: [PATCH 099/180] Remove ClickAwayListener, handle it within background of component list --- .../ContentEditor/ContentEditor.tsx | 93 +++++++++++-------- .../components/BaseComponent.tsx | 90 ++++++++---------- 2 files changed, 93 insertions(+), 90 deletions(-) diff --git a/src/components/ContentEditor/ContentEditor.tsx b/src/components/ContentEditor/ContentEditor.tsx index e85e684..006c422 100644 --- a/src/components/ContentEditor/ContentEditor.tsx +++ b/src/components/ContentEditor/ContentEditor.tsx @@ -8,10 +8,11 @@ import { makeStyles, Theme, Grid } from "@material-ui/core"; import ComponentList from "./ComponentList"; import ComponentSelector from "./ComponentSelector"; import { subscribeChapterById_chapter } from "queries/__generated__/subscribeChapterById"; -import { useSelector } from "react-redux"; +import { useSelector, useDispatch } from "react-redux"; import { TAppState } from "reducers"; -import { IContentEditorState } from "reducers/contentEditorSlice"; +import { IContentEditorState, actions } from "reducers/contentEditorSlice"; import { CREATE_COMPONENT, UPDATE_COMPONENT } from "queries/component"; +import Settings from "./Settings"; export const TOP_LEVEL_COMPONENT_TYPE = "top-level-component"; @@ -28,10 +29,16 @@ interface Props { const ContentEditor = ({ data }: Props) => { const classes = useStyles(); + const dispatch = useDispatch(); const { selectedComponent } = useSelector( state => state.contentEditor ); + const unselectComponent = React.useCallback( + () => dispatch(actions.setSelectedComponent()), + [dispatch] + ); + const [ createComponent, { loading: createLoading, error: createError } @@ -87,42 +94,54 @@ const ContentEditor = ({ data }: Props) => { } } + const handleClickAway = ( + event: React.MouseEvent + ) => { + event.stopPropagation(); + event.preventDefault(); + unselectComponent(); + }; + return ( - - - {provided => ( -
- - {provided.placeholder} -
- )} -
- {/* This is the "top-level" droppable area. Note that draggables can only be dropped within a droppable with the same type! */} - - {(provided, snapshot) => ( - - - {provided.placeholder} - - )} - -
+ <> + + + {provided => ( +
+ + {provided.placeholder} +
+ )} +
+ {/* This is the "top-level" droppable area. Note that draggables can only be dropped within a droppable with the same type! */} + + {(provided, snapshot) => ( + + + {provided.placeholder} + + )} + +
+ + ); }; diff --git a/src/components/ContentEditor/components/BaseComponent.tsx b/src/components/ContentEditor/components/BaseComponent.tsx index 671dec1..e26c46f 100644 --- a/src/components/ContentEditor/components/BaseComponent.tsx +++ b/src/components/ContentEditor/components/BaseComponent.tsx @@ -45,11 +45,6 @@ const BaseComponent = ({ level, index, data }: Props) => { [data, dispatch] ); - const unselectComponent = React.useCallback( - () => dispatch(actions.setSelectedComponent()), - [dispatch] - ); - const isSelected = (selectedComponent && selectedComponent.id === data.id) || false; @@ -61,12 +56,6 @@ const BaseComponent = ({ level, index, data }: Props) => { selectComponent(); }; - const handleClickAway = (event: React.MouseEvent) => { - event.stopPropagation(); - event.preventDefault(); - unselectComponent(); - }; - return ( { disableInteractiveElementBlocking > {(provided, snapshot) => ( - - - + + - {data.children.length ? ( - // - - {(provided, snapshot) => ( - - - {provided.placeholder} - - )} - - ) : // - null} - - + {data.children.length ? ( + // + + {(provided, snapshot) => ( + + + {provided.placeholder} + + )} + + ) : // + null} +
)} ); From bbdb4e951ec47a7d1203bae9b5802ae9c3767a2c Mon Sep 17 00:00:00 2001 From: David Friederich Date: Sun, 18 Aug 2019 12:26:36 +0200 Subject: [PATCH 100/180] Refactor state management to use apollo cache as source of truth. --- .../ContentEditor/BaseComponent.tsx | 117 ++++++++++++++++++ .../{components => }/ComponentHeader.tsx | 33 ++--- .../ContentEditor/ComponentList.tsx | 79 ++++++++---- .../ContentEditor/ComponentSelector.tsx | 23 ++-- .../ContentEditor/ContentEditor.tsx | 51 ++++---- src/components/ContentEditor/Settings.tsx | 92 ++++++++++++++ .../components/BaseComponent.tsx | 109 ---------------- .../components/TextComponent.tsx | 23 ++++ .../components/TitleComponent.tsx | 23 ++++ src/components/Discussion.tsx | 4 +- src/locales/de-CH.json | 3 + src/locales/en-US.json | 3 + src/pages/Chapter/SubChapterDetail.tsx | 12 +- src/queries/component.ts | 22 ++++ 14 files changed, 401 insertions(+), 193 deletions(-) create mode 100644 src/components/ContentEditor/BaseComponent.tsx rename src/components/ContentEditor/{components => }/ComponentHeader.tsx (74%) create mode 100644 src/components/ContentEditor/Settings.tsx delete mode 100644 src/components/ContentEditor/components/BaseComponent.tsx create mode 100644 src/components/ContentEditor/components/TextComponent.tsx create mode 100644 src/components/ContentEditor/components/TitleComponent.tsx diff --git a/src/components/ContentEditor/BaseComponent.tsx b/src/components/ContentEditor/BaseComponent.tsx new file mode 100644 index 0000000..b7b0184 --- /dev/null +++ b/src/components/ContentEditor/BaseComponent.tsx @@ -0,0 +1,117 @@ +import * as React from "react"; +import classNames from "classnames"; +import { Draggable, Droppable } from "react-beautiful-dnd"; + +import { makeStyles } from "@material-ui/styles"; +import { Theme } from "@material-ui/core/styles"; +import { Grid } from "@material-ui/core"; + +import { subscribeChapterById_chapter_components } from "queries/__generated__/subscribeChapterById"; +import ComponentList from "./ComponentList"; +import ComponentHeader from "./ComponentHeader"; +import Text from "components/Text"; +import { useApolloClient } from "@apollo/react-hooks"; + +/** + * Default that should get rendered in the settings widget + */ +export const BaseSettings = () => Not yet implemented!; + +const useStyles = makeStyles((theme: Theme) => ({ + container: { + border: "solid", + borderWidth: 2, + borderColor: theme.palette.grey[200], + marginBottom: theme.spacing(2) + }, + selected: { + borderColor: theme.palette.primary.light + } +})); + +export interface BaseComponentProps { + /** + * Specifies the "depth" of the list the component belongs to + */ + level: number; + index: number; + data: subscribeChapterById_chapter_components; + preview?: React.ReactNode; + selectedComponentId: string; +} + +const BaseComponent = ({ + level, + index, + data, + preview, + selectedComponentId +}: BaseComponentProps) => { + const classes = useStyles(); + const client = useApolloClient(); + + const handleOnComponentClick = ( + event: React.MouseEvent + ) => { + event.stopPropagation(); + event.preventDefault(); + client.writeData({ data: { selectedComponentId: data.id } }); + }; + + const isSelected = selectedComponentId === data.id || false; + + return ( + <> + + {(provided, snapshot) => ( + + + {preview} + + {data.children.length ? ( + + {(provided, snapshot) => ( + + + {provided.placeholder} + + )} + + ) : null} + + )} + + + ); +}; + +export default BaseComponent; diff --git a/src/components/ContentEditor/components/ComponentHeader.tsx b/src/components/ContentEditor/ComponentHeader.tsx similarity index 74% rename from src/components/ContentEditor/components/ComponentHeader.tsx rename to src/components/ContentEditor/ComponentHeader.tsx index de7b412..452289e 100644 --- a/src/components/ContentEditor/components/ComponentHeader.tsx +++ b/src/components/ContentEditor/ComponentHeader.tsx @@ -1,23 +1,13 @@ import * as React from "react"; -import classNames from "classnames"; -import { useMutation } from "@apollo/react-hooks"; -import { useDispatch } from "react-redux"; +import { useMutation, useApolloClient } from "@apollo/react-hooks"; import { makeStyles } from "@material-ui/styles"; import { Theme } from "@material-ui/core/styles"; -import { - Icon, - Typography, - Grid, - IconButton, - Menu, - MenuItem -} from "@material-ui/core"; +import { Icon, Grid, IconButton, Menu, MenuItem } from "@material-ui/core"; import { DragHandle, MoreVert } from "@material-ui/icons"; import { subscribeChapterById_chapter_components } from "queries/__generated__/subscribeChapterById"; import { DELETE_COMPONENT } from "queries/component"; -import { actions } from "reducers/contentEditorSlice"; const useStyles = makeStyles((theme: Theme) => ({ container: { @@ -35,20 +25,14 @@ interface Props { const ComponentHeader = ({ provided, data }: Props) => { const classes = useStyles(); + const client = useApolloClient(); const [anchorEl, setAnchorEl] = React.useState(null); const open = Boolean(anchorEl); - const dispatch = useDispatch(); - const unselectComponent = React.useCallback( - () => dispatch(actions.setSelectedComponent()), - [dispatch] + const [deleteComponent, { loading: deleteLoading }] = useMutation( + DELETE_COMPONENT ); - const [ - deleteComponent, - { loading: deleteLoading, error: deleteError } - ] = useMutation(DELETE_COMPONENT); - function handleClick(event: React.MouseEvent) { event.stopPropagation(); event.preventDefault(); @@ -65,7 +49,7 @@ const ComponentHeader = ({ provided, data }: Props) => { event.stopPropagation(); event.preventDefault(); deleteComponent({ variables: { id: data.id } }); - unselectComponent(); + client.writeData({ data: { selectedComponentId: null } }); } return ( @@ -74,7 +58,6 @@ const ComponentHeader = ({ provided, data }: Props) => { {data.type.icon} - {data.data} { open={open} onClose={handleClose} > - Delete + + Delete + ))}
diff --git a/src/components/ContentEditor/ComponentList.tsx b/src/components/ContentEditor/ComponentList.tsx index 3eb45bd..f8965c3 100644 --- a/src/components/ContentEditor/ComponentList.tsx +++ b/src/components/ContentEditor/ComponentList.tsx @@ -1,11 +1,37 @@ import * as React from "react"; -import { List, ListSubheader } from "@material-ui/core"; +import { List } from "@material-ui/core"; -import BaseComponent from "./components/BaseComponent"; +import BaseComponent, { BaseComponentProps } from "./BaseComponent"; import { subscribeChapterById_chapter_components } from "queries/__generated__/subscribeChapterById"; +import TitleComponent from "./components/TitleComponent"; +import { useQuery } from "@apollo/react-hooks"; +import { GET_LOCAL_SELECTED_COMPONENT_ID } from "queries/component"; -interface Props { +// This defines a mapping of component type names to the React Component, used then to render the content and the settings +export const componentTypes: { [key: string]: any } = { + default: BaseComponent, + Title: TitleComponent, + Text: BaseComponent, + Dialogue: BaseComponent +}; + +interface ComponentWrapperProps { + /** + * The name of the component type as defined on the backend and added in the componentTypes mapping above + */ + type: string; +} + +const ComponentWrapper = ({ + type, + ...otherProps +}: ComponentWrapperProps & BaseComponentProps) => { + const Component = componentTypes[type] || componentTypes.default; + return ; +}; + +interface ComponentListProps { /** * Specifies the depth the list resides, i.e. for top-level components it is 0 */ @@ -19,23 +45,34 @@ interface Props { /** * Wrap with React Memo to avoid rerender */ -const ComponentList = React.memo(({ level, components }) => { - return ( - - {/* TODO(df): Extract logic into this componentHeader */} - {/* */} - {components.map( - (component: subscribeChapterById_chapter_components, index: number) => ( - - ) - )} - - ); -}); +const ComponentList = React.memo( + ({ level, components }) => { + const { data: selectedComponentData } = useQuery( + GET_LOCAL_SELECTED_COMPONENT_ID + ); + + const { selectedComponentId } = selectedComponentData; + + return ( + + {components.map( + ( + component: subscribeChapterById_chapter_components, + index: number + ) => ( + + ) + )} + + ); + } +); export default ComponentList; diff --git a/src/components/ContentEditor/ComponentSelector.tsx b/src/components/ContentEditor/ComponentSelector.tsx index 90dd000..2b29301 100644 --- a/src/components/ContentEditor/ComponentSelector.tsx +++ b/src/components/ContentEditor/ComponentSelector.tsx @@ -1,5 +1,4 @@ import * as React from "react"; -import { useSelector } from "react-redux"; import { Draggable, DraggableProvided, @@ -21,12 +20,14 @@ import { getAllComponentTypes, getAllComponentTypes_types } from "queries/__generated__/getAllComponentTypes"; -import { TAppState } from "reducers"; -import { IContentEditorState } from "reducers/contentEditorSlice"; import { getComponentTypeById, getComponentTypeById_type_children } from "queries/__generated__/getComponentTypeById"; +import { + GET_LOCAL_SELECTED_COMPONENT_ID, + GET_SELECTED_COMPONENT +} from "queries/component"; const useStyles = makeStyles((theme: Theme) => ({ container: { @@ -109,14 +110,20 @@ function isByIdResult( return (result as getComponentTypeById).type !== undefined; } -interface Props {} - -const ComponentSelector = ({ }: Props) => { +const ComponentSelector = () => { const classes = useStyles(); - const { selectedComponent } = useSelector( - state => state.contentEditor + + const { data: selectedComponentIdData } = useQuery( + GET_LOCAL_SELECTED_COMPONENT_ID ); + const { selectedComponentId = undefined } = selectedComponentIdData || {}; + const { data: selectedComponentData } = useQuery(GET_SELECTED_COMPONENT, { + skip: !selectedComponentId + }); + const { component: selectedComponent = undefined } = + selectedComponentData || {}; + // Get either the top level components or const { data, loading, error } = useQuery< getAllComponentTypes | getComponentTypeById >( diff --git a/src/components/ContentEditor/ContentEditor.tsx b/src/components/ContentEditor/ContentEditor.tsx index 006c422..377d633 100644 --- a/src/components/ContentEditor/ContentEditor.tsx +++ b/src/components/ContentEditor/ContentEditor.tsx @@ -1,17 +1,19 @@ import * as React from "react"; import { DragDropContext, Droppable, DropResult } from "react-beautiful-dnd"; import classNames from "classnames"; -import { useMutation } from "@apollo/react-hooks"; +import { useMutation, useApolloClient, useQuery } from "@apollo/react-hooks"; -import { makeStyles, Theme, Grid } from "@material-ui/core"; +import { makeStyles, Theme, Grid, CircularProgress } from "@material-ui/core"; import ComponentList from "./ComponentList"; import ComponentSelector from "./ComponentSelector"; import { subscribeChapterById_chapter } from "queries/__generated__/subscribeChapterById"; -import { useSelector, useDispatch } from "react-redux"; -import { TAppState } from "reducers"; -import { IContentEditorState, actions } from "reducers/contentEditorSlice"; -import { CREATE_COMPONENT, UPDATE_COMPONENT } from "queries/component"; +import { + CREATE_COMPONENT, + UPDATE_COMPONENT, + GET_SELECTED_COMPONENT, + GET_LOCAL_SELECTED_COMPONENT_ID +} from "queries/component"; import Settings from "./Settings"; export const TOP_LEVEL_COMPONENT_TYPE = "top-level-component"; @@ -29,25 +31,27 @@ interface Props { const ContentEditor = ({ data }: Props) => { const classes = useStyles(); - const dispatch = useDispatch(); - const { selectedComponent } = useSelector( - state => state.contentEditor - ); + const client = useApolloClient(); - const unselectComponent = React.useCallback( - () => dispatch(actions.setSelectedComponent()), - [dispatch] + const { data: selectedComponentIdData } = useQuery( + GET_LOCAL_SELECTED_COMPONENT_ID ); - const [ - createComponent, - { loading: createLoading, error: createError } - ] = useMutation(CREATE_COMPONENT); + const { selectedComponentId } = selectedComponentIdData; + + const { data: selectedComponentData } = useQuery(GET_SELECTED_COMPONENT, { + skip: !selectedComponentId + }); + + const { selectedComponent = undefined } = selectedComponentData || {}; - const [ - updateComponent, - { loading: updateLoading, error: updateError } - ] = useMutation(UPDATE_COMPONENT); + const [createComponent, { loading: createLoading }] = useMutation( + CREATE_COMPONENT + ); + + const [updateComponent, { loading: updateLoading }] = useMutation( + UPDATE_COMPONENT + ); /** * Is called when the drag ends. Main function that handles all the logic related to DragAndDrop @@ -99,7 +103,7 @@ const ContentEditor = ({ data }: Props) => { ) => { event.stopPropagation(); event.preventDefault(); - unselectComponent(); + client.writeData({ data: { selectedComponentId: null } }); }; return ( @@ -134,13 +138,14 @@ const ContentEditor = ({ data }: Props) => { className={classNames(classes.container)} onClick={handleClickAway} > + {createLoading || updateLoading ? : null} {provided.placeholder} + )} - ); }; diff --git a/src/components/ContentEditor/Settings.tsx b/src/components/ContentEditor/Settings.tsx new file mode 100644 index 0000000..228e2e3 --- /dev/null +++ b/src/components/ContentEditor/Settings.tsx @@ -0,0 +1,92 @@ +import * as React from "react"; + +import { Theme, makeStyles } from "@material-ui/core/styles"; +import { Grid, Drawer, Button } from "@material-ui/core"; + +import Text from "components/Text"; +import { Settings as SettingsIcon, Save as SaveIcon } from "@material-ui/icons"; +import { BaseSettings } from "./BaseComponent"; +import { TitleSettings } from "./components/TitleComponent"; +import { TextSettings } from "./components/TextComponent"; +import { useQuery } from "@apollo/react-hooks"; +import { + GET_SELECTED_COMPONENT, + GET_LOCAL_SELECTED_COMPONENT_ID +} from "queries/component"; + +// This defines a mapping of component setting type names to the React Component, used then to render the content and the settings +export const settingTypes: { [key: string]: any } = { + default: BaseSettings, + Title: TitleSettings, + Text: TextSettings +}; + +interface SettingsContentProps { + type: string; +} + +const SettingsContent = ({ type }: SettingsContentProps) => { + const Component = settingTypes[type] || settingTypes.default; + return ; +}; + +const useStyles = makeStyles((theme: Theme) => ({ + drawer: { + backgroundColor: theme.palette.grey[400] + }, + container: { + height: "100px", + padding: theme.spacing(5), + backgroundColor: theme.palette.grey[400] + } +})); + +const Settings = () => { + const classes = useStyles(); + + /* + const { selectedComponent } = useSelector( + state => state.contentEditor + ); + */ + + const { data: selectedComponentIdData } = useQuery( + GET_LOCAL_SELECTED_COMPONENT_ID + ); + const { selectedComponentId } = selectedComponentIdData; + const { data } = useQuery(GET_SELECTED_COMPONENT, { + skip: !selectedComponentId + }); + const { component: selectedComponent = undefined } = data || {}; + + return ( + + + + + + chapterEditor:settingsTitle + + + + + + + {selectedComponent ? ( + + ) : null} + + + + ); +}; + +export default Settings; diff --git a/src/components/ContentEditor/components/BaseComponent.tsx b/src/components/ContentEditor/components/BaseComponent.tsx deleted file mode 100644 index e26c46f..0000000 --- a/src/components/ContentEditor/components/BaseComponent.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import * as React from "react"; -import classNames from "classnames"; -import { Draggable, Droppable } from "react-beautiful-dnd"; -import { useDispatch, useSelector } from "react-redux"; - -import { makeStyles } from "@material-ui/styles"; -import { Theme } from "@material-ui/core/styles"; -import { Grid, ClickAwayListener } from "@material-ui/core"; - -import { actions, IContentEditorState } from "reducers/contentEditorSlice"; -import { subscribeChapterById_chapter_components } from "queries/__generated__/subscribeChapterById"; -import ComponentList from "../ComponentList"; -import { TAppState } from "reducers"; -import ComponentHeader from "./ComponentHeader"; - -const useStyles = makeStyles((theme: Theme) => ({ - container: { - border: "solid", - borderWidth: 2, - borderColor: theme.palette.grey[200], - marginBottom: theme.spacing(2) - }, - selected: { - borderColor: theme.palette.primary.light - } -})); - -interface Props { - /** - * Specifies the "depth" of the list the component belongs to - */ - level: number; - index: number; - data: subscribeChapterById_chapter_components; -} - -const BaseComponent = ({ level, index, data }: Props) => { - const classes = useStyles(); - const dispatch = useDispatch(); - const { selectedComponent } = useSelector( - state => state.contentEditor - ); - const selectComponent = React.useCallback( - () => dispatch(actions.setSelectedComponent(data)), - [data, dispatch] - ); - - const isSelected = - (selectedComponent && selectedComponent.id === data.id) || false; - - const handleOnComponentClick = ( - event: React.MouseEvent - ) => { - event.stopPropagation(); - event.preventDefault(); - selectComponent(); - }; - - return ( - - {(provided, snapshot) => ( - - - - {data.children.length ? ( - // - - {(provided, snapshot) => ( - - - {provided.placeholder} - - )} - - ) : // - null} - - )} - - ); -}; - -export default BaseComponent; diff --git a/src/components/ContentEditor/components/TextComponent.tsx b/src/components/ContentEditor/components/TextComponent.tsx new file mode 100644 index 0000000..8860f45 --- /dev/null +++ b/src/components/ContentEditor/components/TextComponent.tsx @@ -0,0 +1,23 @@ +import * as React from "react"; + +import { makeStyles } from "@material-ui/styles"; +import { Theme } from "@material-ui/core/styles"; + +import BaseComponent, { BaseComponentProps } from "../BaseComponent"; +import Text from "components/Text"; + +export const TextSettings = () => Hello Text?; + +const useStyles = makeStyles((theme: Theme) => ({})); + +export interface TitleComponentProps extends BaseComponentProps {} + +const TextComponent = ({ ...otherProps }: TitleComponentProps) => { + const classes = useStyles(); + + const preview = ... TEXT; + + return ; +}; + +export default TextComponent; diff --git a/src/components/ContentEditor/components/TitleComponent.tsx b/src/components/ContentEditor/components/TitleComponent.tsx new file mode 100644 index 0000000..677418b --- /dev/null +++ b/src/components/ContentEditor/components/TitleComponent.tsx @@ -0,0 +1,23 @@ +import * as React from "react"; + +import { makeStyles } from "@material-ui/styles"; +import { Theme } from "@material-ui/core/styles"; + +import BaseComponent, { BaseComponentProps } from "../BaseComponent"; +import Text from "components/Text"; + +export const TitleSettings = () => Hello title?; + +const useStyles = makeStyles((theme: Theme) => ({})); + +export interface TitleComponentProps extends BaseComponentProps {} + +const TitleComponent = ({ ...otherProps }: TitleComponentProps) => { + const classes = useStyles(); + + const preview = Preview...; + + return ; +}; + +export default TitleComponent; diff --git a/src/components/Discussion.tsx b/src/components/Discussion.tsx index e9a0fe6..12cc0c2 100644 --- a/src/components/Discussion.tsx +++ b/src/components/Discussion.tsx @@ -154,7 +154,9 @@ const Discussion = ({ classes, data }: Props) => { > {t("resolve")} - {t("delete")} + + {t("delete")} + {data && data.active ? ( - + {selectedComponent ? ( - + ) : null} diff --git a/src/components/ContentEditor/components/TextComponent.tsx b/src/components/ContentEditor/components/TextComponent.tsx index 8860f45..94821d9 100644 --- a/src/components/ContentEditor/components/TextComponent.tsx +++ b/src/components/ContentEditor/components/TextComponent.tsx @@ -1,23 +1,72 @@ import * as React from "react"; +import { Formik, Form, Field } from "formik"; +import * as Yup from "yup"; +import { useTranslation } from "react-i18next"; +import { TextField } from "formik-material-ui"; import { makeStyles } from "@material-ui/styles"; import { Theme } from "@material-ui/core/styles"; -import BaseComponent, { BaseComponentProps } from "../BaseComponent"; +import BaseComponent, { + BaseComponentProps, + BaseSettingsProps +} from "../BaseComponent"; import Text from "components/Text"; +import i18next from "i18next"; -export const TextSettings = () => Hello Text?; +/** + * Validation Schema definition of the input fields of this component + */ +const TextSchema = Yup.object().shape({ + text: Yup.string().required(i18next.t("required")) +}); + +export interface TextSettingsProps extends BaseSettingsProps {} + +/** + * Setting widget's dynamic component view + */ +export const TextSettings = React.forwardRef( + (props, ref) => { + const { data, onSubmit } = props; + const { t } = useTranslation(); + + return ( + ( + + + + )} + /> + ); + } +); const useStyles = makeStyles((theme: Theme) => ({})); -export interface TitleComponentProps extends BaseComponentProps {} +export interface TextComponentProps extends BaseComponentProps {} -const TextComponent = ({ ...otherProps }: TitleComponentProps) => { +/** + * How the component should get rendered in the editor + */ +const TextComponent = ({ data, ...otherProps }: TextComponentProps) => { const classes = useStyles(); + const preview = {data.data}; - const preview = ... TEXT; - - return ; + return ; }; export default TextComponent; diff --git a/src/components/ContentEditor/components/TitleComponent.tsx b/src/components/ContentEditor/components/TitleComponent.tsx index 677418b..e660a51 100644 --- a/src/components/ContentEditor/components/TitleComponent.tsx +++ b/src/components/ContentEditor/components/TitleComponent.tsx @@ -1,23 +1,73 @@ import * as React from "react"; +import { Formik, Form, Field } from "formik"; +import * as Yup from "yup"; +import { useTranslation } from "react-i18next"; +import { TextField } from "formik-material-ui"; import { makeStyles } from "@material-ui/styles"; import { Theme } from "@material-ui/core/styles"; -import BaseComponent, { BaseComponentProps } from "../BaseComponent"; +import BaseComponent, { + BaseComponentProps, + BaseSettingsProps +} from "../BaseComponent"; import Text from "components/Text"; +import i18next from "i18next"; -export const TitleSettings = () => Hello title?; +/** + * Validation Schema definition of the input fields of this component + */ +const TitleSchema = Yup.object().shape({ + title: Yup.string().required(i18next.t("required")) +}); + +export interface TitleSettingsProps extends BaseSettingsProps {} + +/** + * Setting widget's dynamic component view + */ +export const TitleSettings = React.forwardRef( + (props, ref) => { + const { data, onSubmit } = props; + const { t } = useTranslation(); + + return ( + ( +
+ + + )} + /> + ); + } +); const useStyles = makeStyles((theme: Theme) => ({})); export interface TitleComponentProps extends BaseComponentProps {} -const TitleComponent = ({ ...otherProps }: TitleComponentProps) => { +/** + * How the component should get rendered in the editor + */ +const TitleComponent = ({ data, ...otherProps }: TitleComponentProps) => { const classes = useStyles(); - const preview = Preview...; + const preview = {data.data}; - return ; + return ; }; export default TitleComponent; From 9992caaf7ce2e812ed27ce9846ed8aa2079a959d Mon Sep 17 00:00:00 2001 From: Yanick Schraner Date: Sat, 28 Sep 2019 15:02:31 +0200 Subject: [PATCH 105/180] Fix issues after merging --- src/components/WordCard.tsx | 4 ++-- src/pages/WordGroup/WordGroup.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/WordCard.tsx b/src/components/WordCard.tsx index c8e831d..01d1509 100644 --- a/src/components/WordCard.tsx +++ b/src/components/WordCard.tsx @@ -33,12 +33,12 @@ const WordCard = ({ classes, word, id }: Props) => { color="textSecondary" gutterBottom > - {word.translations.map(t => ( + {word.translations.length ? word.translations.map(t => ( <> {t.text}
- )): t("words:emptyWord" } + )): t("words:emptyWord") } diff --git a/src/pages/WordGroup/WordGroup.tsx b/src/pages/WordGroup/WordGroup.tsx index e07551e..62c4926 100644 --- a/src/pages/WordGroup/WordGroup.tsx +++ b/src/pages/WordGroup/WordGroup.tsx @@ -35,7 +35,7 @@ const WordGroup = ({ classes, match }: Props) => { variables: { id: match.params.id }, - skip: match.params.id === "new" + // skip: match.params.id === "new" } ); From fa464443fbe23855432d9016bd502121de228a19 Mon Sep 17 00:00:00 2001 From: Yanick Schraner Date: Sat, 28 Sep 2019 16:55:10 +0200 Subject: [PATCH 106/180] Use new Hasura queries --- src/__generated__/globalTypes.ts | 18 ++- src/components/VoggiChapterCard.tsx | 6 +- src/components/WordGroupCard.tsx | 4 +- src/pages/WordGroup/ChapterWordGroups.tsx | 31 +++-- src/pages/WordGroup/WordGroups.tsx | 20 ++-- .../__generated__/chapters_wordGroups.ts | 107 ++++++++++++------ .../subscribeChaptersWordGroupsByChapterId.ts | 78 +++++++++++++ src/queries/chapters.ts | 81 +++++++------ 8 files changed, 242 insertions(+), 103 deletions(-) create mode 100644 src/queries/__generated__/subscribeChaptersWordGroupsByChapterId.ts diff --git a/src/__generated__/globalTypes.ts b/src/__generated__/globalTypes.ts index b6395e5..ebae5ab 100644 --- a/src/__generated__/globalTypes.ts +++ b/src/__generated__/globalTypes.ts @@ -97,6 +97,7 @@ export enum api_component_update_column { fk_locked_by_id = "fk_locked_by_id", id = "id", locked_ts = "locked_ts", + order_in_chapter = "order_in_chapter", state = "state", updated = "updated", } @@ -424,8 +425,8 @@ export interface api_component_arr_rel_insert_input { */ export interface api_component_insert_input { chapter?: api_chapter_obj_rel_insert_input | null; + children?: api_component_arr_rel_insert_input | null; comments?: api_comment_arr_rel_insert_input | null; - componentType?: api_componenttype_obj_rel_insert_input | null; created?: any | null; data?: string | null; fk_chapter_id?: any | null; @@ -435,9 +436,11 @@ export interface api_component_insert_input { id?: any | null; lockedByUser?: api_profile_obj_rel_insert_input | null; locked_ts?: any | null; - parentComponent?: api_component_obj_rel_insert_input | null; + order_in_chapter?: number | null; + parent?: api_component_obj_rel_insert_input | null; state?: string | null; texts?: api_text_arr_rel_insert_input | null; + type?: api_componenttype_obj_rel_insert_input | null; updated?: any | null; } @@ -457,17 +460,27 @@ export interface api_component_on_conflict { update_columns: api_component_update_column[]; } +/** + * input type for inserting array relation for remote table "api_componenttype" + */ +export interface api_componenttype_arr_rel_insert_input { + data: api_componenttype_insert_input[]; + on_conflict?: api_componenttype_on_conflict | null; +} + /** * input type for inserting data into table "api_componenttype" */ export interface api_componenttype_insert_input { base?: boolean | null; + children?: api_componenttype_arr_rel_insert_input | null; created?: any | null; fk_parent_type_id?: any | null; icon?: string | null; id?: any | null; label?: string | null; name?: string | null; + parent?: api_componenttype_obj_rel_insert_input | null; schema?: string | null; updated?: any | null; } @@ -594,6 +607,7 @@ export interface api_translation_insert_input { fk_language_id?: any | null; fk_text_id?: any | null; id?: any | null; + language?: api_language_obj_rel_insert_input | null; text_field?: string | null; updated?: any | null; valid?: boolean | null; diff --git a/src/components/VoggiChapterCard.tsx b/src/components/VoggiChapterCard.tsx index ef84bc0..a83ebae 100644 --- a/src/components/VoggiChapterCard.tsx +++ b/src/components/VoggiChapterCard.tsx @@ -9,10 +9,10 @@ import CardContent from "@material-ui/core/CardContent"; import CardActionArea from "@material-ui/core/CardActionArea"; import { styles } from "styles"; -import { chapters_wordGroups_chapters_edges_node } from "../queries/__generated__/chapters_wordGroups"; +import { chapters_wordGroups_chapters } from "../queries/__generated__/chapters_wordGroups"; interface Props extends WithStyles { - chapter: chapters_wordGroups_chapters_edges_node; + chapter: chapters_wordGroups_chapters; } const VoggiChapterCard = ({ classes, chapter }: Props) => { @@ -35,7 +35,7 @@ const VoggiChapterCard = ({ classes, chapter }: Props) => { {t("chapter:nWordGroups")}{" "} - {chapter.wordGroups ? chapter.wordGroups.edges.length : 0} + {chapter.wordgroups ? chapter.wordgroups.length : 0} diff --git a/src/components/WordGroupCard.tsx b/src/components/WordGroupCard.tsx index def37a8..c93e60c 100644 --- a/src/components/WordGroupCard.tsx +++ b/src/components/WordGroupCard.tsx @@ -9,13 +9,13 @@ import { useTranslation } from "react-i18next"; import CardActionArea from "@material-ui/core/CardActionArea"; import { styles } from "styles"; -import { chaptersWordGroupsByChapterId_chapter_wordGroups_edges_node } from "../queries/__generated__/chaptersWordGroupsByChapterId"; import { subscribeWordGroups_wordGroups } from "queries/__generated__/subscribeWordGroups"; +import {subscribeChaptersWordGroupsByChapterId_chapters_wordgroups} from "../queries/__generated__/subscribeChaptersWordGroupsByChapterId"; interface Props extends WithStyles { wordGroup: | subscribeWordGroups_wordGroups - | chaptersWordGroupsByChapterId_chapter_wordGroups_edges_node; + | subscribeChaptersWordGroupsByChapterId_chapters_wordgroups; } const WordGroupCard = ({ classes, wordGroup }: Props) => { diff --git a/src/pages/WordGroup/ChapterWordGroups.tsx b/src/pages/WordGroup/ChapterWordGroups.tsx index 0f68b1f..c0d6004 100644 --- a/src/pages/WordGroup/ChapterWordGroups.tsx +++ b/src/pages/WordGroup/ChapterWordGroups.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import {useTranslation} from "react-i18next"; import {withStyles, WithStyles} from "@material-ui/core/styles"; -import {useQuery} from "@apollo/react-hooks"; +import {useSubscription} from "@apollo/react-hooks"; import {RouteComponentProps} from "react-router-dom"; import Grid from "@material-ui/core/Grid"; import AddIcon from "@material-ui/icons/Add"; @@ -12,11 +12,10 @@ import BusyOrErrorCard from "components/BusyOrErrorCard"; import SectionCardContainer from "../../components/SectionCardContainer"; import Section from "../../components/Section"; import {GET_CHAPTER_WORDGROUPS_BY_CHAPTER_ID} from "../../queries/chapters"; -import {convertGlobalToDbId} from "../../helpers"; import { - chaptersWordGroupsByChapterId, - chaptersWordGroupsByChapterId_chapter_wordGroups_edges -} from "../../queries/__generated__/chaptersWordGroupsByChapterId"; + subscribeChaptersWordGroupsByChapterId, + subscribeChaptersWordGroupsByChapterId_chapters_wordgroups +} from "../../queries/__generated__/subscribeChaptersWordGroupsByChapterId"; import WordGroupCard from "../../components/WordGroupCard"; import LinkCard from "../../components/LinkCard"; @@ -30,31 +29,31 @@ interface Props extends RouteComponentProps, WithStyles { const {t} = useTranslation(); - const {data, error, loading} = useQuery(GET_CHAPTER_WORDGROUPS_BY_CHAPTER_ID, { + const {data, error, loading} = useSubscription(GET_CHAPTER_WORDGROUPS_BY_CHAPTER_ID, { variables: { - id: convertGlobalToDbId(match.params.id) + id: match.params.id }, - skip: match.params.id === "new" + // skip: match.params.id === "new" }); // Note: MUI links together with react-router-dom and Typescript are a bit tricky due to their dynamic nature // See the discussion and provided solutions here... https://github.com/mui-org/material-ui/issues/7877 // )} - /> +
diff --git a/src/pages/Settings/Settings.tsx b/src/pages/Settings/Settings.tsx index 84aa554..ff0b38f 100644 --- a/src/pages/Settings/Settings.tsx +++ b/src/pages/Settings/Settings.tsx @@ -90,7 +90,8 @@ const Settings: React.FunctionComponent = ({ classes }) => { initialValues={initialProfile} validationSchema={UserSetupSchema} onSubmit={(values, actions) => handleSave(values, actions)} - render={({ submitForm, values }) => ( + > + {({ values, submitForm }) => (
@@ -108,7 +109,7 @@ const Settings: React.FunctionComponent = ({ classes }) => { )} - /> +
)} diff --git a/src/pages/SetupWizard/SetupWizard.tsx b/src/pages/SetupWizard/SetupWizard.tsx index 5aa0d88..5525211 100644 --- a/src/pages/SetupWizard/SetupWizard.tsx +++ b/src/pages/SetupWizard/SetupWizard.tsx @@ -134,7 +134,8 @@ function SetupWizard({ classes, profile }: Props) { initialValues={initialProfile} validationSchema={UserSetupSchema} onSubmit={(values, actions) => handleSubmit(values, actions)} - render={({ submitForm, values, setFieldValue, validateForm }) => ( + > + {({ submitForm, values, setFieldValue, validateForm }) => (
@@ -171,7 +172,7 @@ function SetupWizard({ classes, profile }: Props) {
)} - /> + ); diff --git a/src/pages/WordGroup/WordEditor.tsx b/src/pages/WordGroup/WordEditor.tsx index 1b58c21..3de1bad 100644 --- a/src/pages/WordGroup/WordEditor.tsx +++ b/src/pages/WordGroup/WordEditor.tsx @@ -74,7 +74,8 @@ const WordEditor = ({ classes, match, values = defaultValues }: Props) => { }} validationSchema={WordGroupSchema} onSubmit={(values, actions) => handleSave(values, actions)} - render={({ submitForm, values, isSubmitting, status }) => ( + > + {({ submitForm, values, isSubmitting, status }) => (
{ )} - /> + diff --git a/src/pages/WordGroup/WordGroupEditor.tsx b/src/pages/WordGroup/WordGroupEditor.tsx index cc309a7..94ac87d 100644 --- a/src/pages/WordGroup/WordGroupEditor.tsx +++ b/src/pages/WordGroup/WordGroupEditor.tsx @@ -68,7 +68,8 @@ const WordGroupEditor = ({ classes }: Props) => { }} validationSchema={WordGroupSchema} onSubmit={(values, actions) => handleSave(values, actions)} - render={({ submitForm, values, isSubmitting, status }) => ( + > + {({ submitForm, values, isSubmitting, status }) => (
{ )} - /> + From 03e80e0d2013341b7879ec2ebb19cae745df72fa Mon Sep 17 00:00:00 2001 From: David Friederich Date: Tue, 19 Nov 2019 16:06:47 +0100 Subject: [PATCH 148/180] Removes stalled react-swipeable-views --- package-lock.json | 163 --------------------------- package.json | 1 - src/pages/Chapter/CommentsWidget.tsx | 23 ++-- 3 files changed, 8 insertions(+), 179 deletions(-) diff --git a/package-lock.json b/package-lock.json index 26f8171..13035a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4342,11 +4342,6 @@ "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" }, - "core-js": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", - "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" - }, "core-js-compat": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.4.1.tgz", @@ -5211,14 +5206,6 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, - "encoding": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", - "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", - "requires": { - "iconv-lite": "~0.4.13" - } - }, "end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -6238,20 +6225,6 @@ "bser": "^2.0.0" } }, - "fbjs": { - "version": "0.8.17", - "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz", - "integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=", - "requires": { - "core-js": "^1.0.0", - "isomorphic-fetch": "^2.1.1", - "loose-envify": "^1.0.0", - "object-assign": "^4.1.0", - "promise": "^7.1.1", - "setimmediate": "^1.0.5", - "ua-parser-js": "^0.7.18" - } - }, "figgy-pudding": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", @@ -7638,26 +7611,6 @@ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" }, - "isomorphic-fetch": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", - "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", - "requires": { - "node-fetch": "^1.0.1", - "whatwg-fetch": ">=0.10.0" - }, - "dependencies": { - "node-fetch": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", - "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", - "requires": { - "encoding": "^0.1.11", - "is-stream": "^1.0.1" - } - } - } - }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -8973,11 +8926,6 @@ "object.assign": "^4.1.0" } }, - "keycode": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz", - "integrity": "sha1-PQr1bce4uOXLqNCpfxByBO7CKwQ=" - }, "killable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", @@ -11332,14 +11280,6 @@ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=" }, - "promise": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", - "requires": { - "asap": "~2.0.3" - } - }, "promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", @@ -11753,16 +11693,6 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.3.tgz", "integrity": "sha512-bOUvMWFQVk5oz8Ded9Xb7WVdEi3QGLC8tH7HmYP0Fdp4Bn3qw0tRFmr5TW6mvahzvmrK4a6bqWGfCevBflP+Xw==" }, - "react-event-listener": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/react-event-listener/-/react-event-listener-0.6.6.tgz", - "integrity": "sha512-+hCNqfy7o9wvO6UgjqFmBzARJS7qrNoda0VqzvOuioEpoEXKutiKuv92dSz6kP7rYLmyHPyYNLesi5t/aH1gfw==", - "requires": { - "@babel/runtime": "^7.2.0", - "prop-types": "^15.6.0", - "warning": "^4.0.1" - } - }, "react-fast-compare": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", @@ -11935,86 +11865,6 @@ } } }, - "react-swipeable-views": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/react-swipeable-views/-/react-swipeable-views-0.13.3.tgz", - "integrity": "sha512-LBHRA5ZouipmoLLwi0cqB8qc7NHLskbXmT1I+ZztC9JfmgKrfichw5R+7q4igQ+5VbaP6jL1vn8BtHW96WYNFQ==", - "requires": { - "@babel/runtime": "7.0.0", - "dom-helpers": "^3.2.1", - "prop-types": "^15.5.4", - "react-swipeable-views-core": "^0.13.1", - "react-swipeable-views-utils": "^0.13.3", - "warning": "^4.0.1" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0.tgz", - "integrity": "sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==", - "requires": { - "regenerator-runtime": "^0.12.0" - } - }, - "regenerator-runtime": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", - "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" - } - } - }, - "react-swipeable-views-core": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/react-swipeable-views-core/-/react-swipeable-views-core-0.13.1.tgz", - "integrity": "sha512-EP8sCvvD7VDiZLglPt9icMuMNu8qLRLk0ab/fB1HXv7lX8ClnwF3UMCM0ZrN3sguSY7CsX3LevducGGsT1VcDg==", - "requires": { - "@babel/runtime": "7.0.0", - "warning": "^4.0.1" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0.tgz", - "integrity": "sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==", - "requires": { - "regenerator-runtime": "^0.12.0" - } - }, - "regenerator-runtime": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", - "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" - } - } - }, - "react-swipeable-views-utils": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/react-swipeable-views-utils/-/react-swipeable-views-utils-0.13.3.tgz", - "integrity": "sha512-CZkJwiNQPISkyTsPMUPiJgwJBrUVd7NC3WSUvx30uwvPb0Sy2w2+tpU51qeYc6YwIhex0s5Eu5YPjK3PDBh+gA==", - "requires": { - "@babel/runtime": "7.0.0", - "fbjs": "^0.8.4", - "keycode": "^2.1.7", - "prop-types": "^15.6.0", - "react-event-listener": "^0.6.0", - "react-swipeable-views-core": "^0.13.1" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0.tgz", - "integrity": "sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==", - "requires": { - "regenerator-runtime": "^0.12.0" - } - }, - "regenerator-runtime": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", - "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" - } - } - }, "react-transition-group": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.3.0.tgz", @@ -14067,11 +13917,6 @@ "integrity": "sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ==", "dev": true }, - "ua-parser-js": { - "version": "0.7.19", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.19.tgz", - "integrity": "sha512-T3PVJ6uz8i0HzPxOF9SWzWAlfN/DavlpQqepn22xgve/5QecC+XMCAtmUNnY7C9StehaV6exjUCI801lOI7QlQ==" - }, "uglify-js": { "version": "3.4.10", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz", @@ -14423,14 +14268,6 @@ "makeerror": "1.0.x" } }, - "warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "requires": { - "loose-envify": "^1.0.0" - } - }, "watchpack": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", diff --git a/package.json b/package.json index 896d16a..a27f5be 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,6 @@ "react-router-dom": "^5.1.2", "react-scripts": "^3.2.0", "react-select": "^3.0.8", - "react-swipeable-views": "^0.13.3", "redux-starter-kit": "^0.6.3", "styled-components": "^4.4.1", "subscriptions-transport-ws": "^0.9.16", diff --git a/src/pages/Chapter/CommentsWidget.tsx b/src/pages/Chapter/CommentsWidget.tsx index cffa312..3517b30 100644 --- a/src/pages/Chapter/CommentsWidget.tsx +++ b/src/pages/Chapter/CommentsWidget.tsx @@ -1,5 +1,4 @@ import * as React from "react"; -import SwipeableViews from "react-swipeable-views"; import { useTranslation } from "react-i18next"; import { @@ -46,20 +45,14 @@ const CommentsWidget = ({ classes, theme }: Props) => { - - - - + + ); }; From 986f3cce31184d639884822f83068d955d6664ab Mon Sep 17 00:00:00 2001 From: David Friederich Date: Tue, 19 Nov 2019 16:07:56 +0100 Subject: [PATCH 149/180] Cleanup --- src/components/ContentEditor/ContentEditor.tsx | 2 +- src/components/ContentEditor/Settings.tsx | 2 +- src/pages/Chapter/CommentsWidget.tsx | 4 ---- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/components/ContentEditor/ContentEditor.tsx b/src/components/ContentEditor/ContentEditor.tsx index 47dcf49..21878fe 100644 --- a/src/components/ContentEditor/ContentEditor.tsx +++ b/src/components/ContentEditor/ContentEditor.tsx @@ -189,7 +189,7 @@ const ContentEditor = ({ data }: Props) => { }; ContentEditor.whyDidYouRender = { - logOnDifferentValues: true + // logOnDifferentValues: true }; export default ContentEditor; diff --git a/src/components/ContentEditor/Settings.tsx b/src/components/ContentEditor/Settings.tsx index 62744f4..146c1dc 100644 --- a/src/components/ContentEditor/Settings.tsx +++ b/src/components/ContentEditor/Settings.tsx @@ -392,7 +392,7 @@ const Settings = () => { }; Settings.whyDidYouRender = { - logOnDifferentValues: true + //logOnDifferentValues: true }; export default Settings; diff --git a/src/pages/Chapter/CommentsWidget.tsx b/src/pages/Chapter/CommentsWidget.tsx index 3517b30..9cd4383 100644 --- a/src/pages/Chapter/CommentsWidget.tsx +++ b/src/pages/Chapter/CommentsWidget.tsx @@ -31,10 +31,6 @@ const CommentsWidget = ({ classes, theme }: Props) => { setActiveCommentTab(newValue); } - function handleTabIndexChange(index: number) { - setActiveCommentTab(index); - } - return ( Date: Tue, 19 Nov 2019 19:42:42 +0100 Subject: [PATCH 150/180] Work on vochi --- package-lock.json | 20 +- package.json | 4 +- src/components/WordCard.tsx | 6 +- src/pages/WordGroup/WordEditor.tsx | 238 ++++++++++-------- src/pages/WordGroup/WordGroup.tsx | 4 +- src/pages/WordGroup/WordGroupEditor.tsx | 73 +++--- src/privateRoutes.ts | 4 +- .../__generated__/subscribeWordById.ts | 45 ++++ src/queries/wordgroups.ts | 10 + 9 files changed, 250 insertions(+), 154 deletions(-) create mode 100644 src/queries/__generated__/subscribeWordById.ts diff --git a/package-lock.json b/package-lock.json index 2446145..650349f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,9 +5,9 @@ "requires": true, "dependencies": { "@apollo/react-common": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@apollo/react-common/-/react-common-3.0.0.tgz", - "integrity": "sha512-EqHASkcmxipy2hU8rja+lD1S1HoTdodKKyJZZ3dgewnAHXnzXnnC3rw1+lkrgXPFsI2n2d2N2LYisD79OCdPmw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@apollo/react-common/-/react-common-3.1.2.tgz", + "integrity": "sha512-6+gTeBZoIyCE6VnHD2EI9Wz+Dm05MBxplUTmVgswzvTe05lUO78SxGgB3XJc9GM/TmNexgCc0eS84vUpG/f6Tg==", "requires": { "ts-invariant": "^0.4.4", "tslib": "^1.10.0" @@ -21,11 +21,11 @@ } }, "@apollo/react-hooks": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@apollo/react-hooks/-/react-hooks-3.0.0.tgz", - "integrity": "sha512-7kaV6rkx2WZjDYcBmp52oyhTxbNn5Jc4AUmsXZVEnDu9uuvNYURA8bLlJNF8yu4zS7ed8D+ZebC0y0bhrz8wIg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@apollo/react-hooks/-/react-hooks-3.1.2.tgz", + "integrity": "sha512-PV5u40E9iwfwM7u61r2P9PTjcGaM3zRwiwrJGDKOKaOn1Y9wTHhKOVEQa7YOsCWciSaMVK1slpKMvQbD2Ypqtw==", "requires": { - "@apollo/react-common": "^3.0.0", + "@apollo/react-common": "^3.1.2", "@wry/equality": "^0.1.9", "ts-invariant": "^0.4.4", "tslib": "^1.10.0" @@ -2239,9 +2239,9 @@ } }, "apollo-client": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/apollo-client/-/apollo-client-2.6.3.tgz", - "integrity": "sha512-DS8pmF5CGiiJ658dG+mDn8pmCMMQIljKJSTeMNHnFuDLV0uAPZoeaAwVFiAmB408Ujqt92oIZ/8yJJAwSIhd4A==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/apollo-client/-/apollo-client-2.6.4.tgz", + "integrity": "sha512-oWOwEOxQ9neHHVZrQhHDbI6bIibp9SHgxaLRVPoGvOFy7OH5XUykZE7hBQAVxq99tQjBzgytaZffQkeWo1B4VQ==", "requires": { "@types/zen-observable": "^0.8.0", "apollo-cache": "1.3.2", diff --git a/package.json b/package.json index cf7f943..ee6d984 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { - "@apollo/react-hooks": "^3.0.0", + "@apollo/react-hooks": "^3.1.2", "@auth0/auth0-spa-js": "^1.1.1", "@material-ui/core": "^4.3.0", "@material-ui/icons": "^4.2.1", @@ -15,7 +15,7 @@ "@types/react-redux": "^7.1.1", "@types/react-select": "^3.0.0", "apollo-cache-inmemory": "^1.6.2", - "apollo-client": "^2.6.3", + "apollo-client": "^2.6.4", "apollo-link": "^1.2.12", "apollo-link-error": "^1.1.11", "apollo-link-http": "^1.5.15", diff --git a/src/components/WordCard.tsx b/src/components/WordCard.tsx index 7e89d7e..c804e9b 100644 --- a/src/components/WordCard.tsx +++ b/src/components/WordCard.tsx @@ -13,10 +13,10 @@ import {useTranslation} from "react-i18next"; interface Props extends WithStyles { word: subscribeWordGroupById_wordGroup_words_word; - id: string; + wordGroupId: string; } -const WordCard = ({ classes, word, id }: Props) => { +const WordCard = ({ classes, word, wordGroupId }: Props) => { const { t } = useTranslation(); // Note: MUI links together with react-router-dom and Typescript are a bit tricky due to their dynamic nature // See the discussion and provided solutions here... https://github.com/mui-org/material-ui/issues/7877 @@ -25,7 +25,7 @@ const WordCard = ({ classes, word, id }: Props) => { { + const {t} = useTranslation(); -const WordEditor = ({ classes, match, values = defaultValues }: Props) => { - const { t } = useTranslation(); + const {loading, data, error} = useSubscription( + GET_WORD_BY_ID, + { + variables: { + id: match.params.wordId + }, + skip: match.params.wordId === "new" + } + ); // TODO: Unfortunately, @apollo/react-hooks doesn't support yet the error, loading object in mutations (unlike with query...) const [upsertWord] = useMutation(UPSERT_WORD); @@ -50,104 +64,120 @@ const WordEditor = ({ classes, match, values = defaultValues }: Props) => { async function handleSave(values: any, actions: FormikActions) { // TODO: This verbose stuff won't be necessary anymore as soon useMutation also returns a error/loading object. try { - await upsertWord({ variables: { input: values } }); - history.push(`/wordgroups/${match.params.id}`); + await upsertWord({variables: {input: values}}); + history.push(`/wordgroups/${match.params.wordgroupId}`); } catch (e) { actions.setSubmitting(false); - actions.setStatus({ response: `${t("serverError")}: ${e.message}` }); + actions.setStatus({response: `${t("serverError")}: ${e.message}`}); } } + const wordDe = data && data.word ? data.word.translations.find((translation: subscribeWordById_word_translations) => { return translation.language.code === 'de'; }) : ""; + const wordCh = data && data.word ? data.word.translations.find((translation: subscribeWordById_word_translations) => { return translation.language.code === 'ch'; }) : ""; + + + return ( - - {t("words:createNewWord")} - - handleSave(values, actions)} - render={({ submitForm, values, isSubmitting, status }) => ( -
- - - - - - - {status && status.response && ( - - )} - - - )} - /> -
-
+ + {data && + data.word ? + ( + {t("words:createNewWord")} + + handleSave(values, actions)} + render={({submitForm, values, isSubmitting, status}) => ( +
+ + + + + + + {status && status.response && ( + + )} + + + )} + /> +
+
) : null}
); }; -export default withStyles(styles, { withTheme: true })(WordEditor); +export default withStyles(styles, {withTheme: true})(WordEditor); diff --git a/src/pages/WordGroup/WordGroup.tsx b/src/pages/WordGroup/WordGroup.tsx index ef9f0eb..124b1c1 100644 --- a/src/pages/WordGroup/WordGroup.tsx +++ b/src/pages/WordGroup/WordGroup.tsx @@ -67,13 +67,13 @@ const WordGroup = ({ classes, match }: Props) => { data.wordGroup.words.map(w => w ? ( - + ) : null )} } helperText="wordGroups:addWordToWordGroup" /> diff --git a/src/pages/WordGroup/WordGroupEditor.tsx b/src/pages/WordGroup/WordGroupEditor.tsx index 399b1d3..9e50628 100644 --- a/src/pages/WordGroup/WordGroupEditor.tsx +++ b/src/pages/WordGroup/WordGroupEditor.tsx @@ -1,40 +1,50 @@ import * as React from "react"; -import { useTranslation } from "react-i18next"; +import {useTranslation} from "react-i18next"; import * as Yup from "yup"; -import { Formik, Form, Field, FormikActions } from "formik"; -import { TextField } from "formik-material-ui"; -import { useMutation } from "@apollo/react-hooks"; +import {Formik, Form, Field, FormikActions} from "formik"; +import {TextField} from "formik-material-ui"; +import {useMutation, useQuery} from "@apollo/react-hooks"; -import { withStyles, WithStyles } from "@material-ui/core/styles"; +import {withStyles, WithStyles} from "@material-ui/core/styles"; import Typography from "@material-ui/core/Typography"; import Card from "@material-ui/core/Card"; import CardContent from "@material-ui/core/CardContent"; import Button from "@material-ui/core/Button"; -import { styles } from "styles"; +import {styles} from "styles"; import i18next from "i18n"; import history from "myHistory"; import ErrorMessage from "components/ErrorMessage"; -import { UPSERT_WORDGROUP } from "../../queries/wordgroups"; +import {UPSERT_WORDGROUP} from "../../queries/wordgroups"; import CustomSelect from "../../components/SearchableMultiSelect"; +import {GET_LANGUAGES} from "../../queries/languages"; +import {getLanguages} from "../../queries/__generated__/getLanguages"; export const WordGroupSchema = Yup.object().shape({ - titleCh: Yup.string() + title_ch: Yup.string() .min(4, i18next.t("tooShort")) .max(50, i18next.t("tooLong")) .required(i18next.t("required")), - titleDe: Yup.string() + title_de: Yup.string() .min(4, i18next.t("tooShort")) .max(50, i18next.t("tooLong")) .required(i18next.t("required")) }); -const words = [{ value: "foo", label: "Foo" }, { value: "bar", label: "Bar" }]; -interface Props extends WithStyles {} +interface Props extends WithStyles { +} + +const WordGroupEditor = ({classes}: Props) => { + const {t} = useTranslation(); + + // const {data, error, loading} = useQuery(GET_LANGUAGES); + // const words = data && data.languages && data.languages.length ? data.languages.map(lang => ( + // { + // value: lang.code, + // label: lang.name + // })) : []; -const WordGroupEditor = ({ classes }: Props) => { - const { t } = useTranslation(); // TODO: const [upsertWordGroup] = useMutation(UPSERT_WORDGROUP); @@ -42,11 +52,12 @@ const WordGroupEditor = ({ classes }: Props) => { async function handleSave(values: any, actions: FormikActions) { // TODO: This verbose stuff won't be necessary anymore as soon useMutation also returns a error/loading object. try { - await upsertWordGroup({ variables: { input: values } }); + // values['words'] = []; + await upsertWordGroup({variables: {input: values}}); history.push("/wordgroups"); } catch (e) { actions.setSubmitting(false); - actions.setStatus({ response: `${t("serverError")}: ${e.message}` }); + actions.setStatus({response: `${t("serverError")}: ${e.message}`}); } } @@ -59,17 +70,17 @@ const WordGroupEditor = ({ classes }: Props) => { handleSave(values, actions)} - render={({ submitForm, values, isSubmitting, status }) => ( + render={({submitForm, values, isSubmitting, status}) => (
{ /> { fullWidth /> - + {/**/} {status && status.response && ( - + )}