diff options
28 files changed, 7633 insertions, 4108 deletions
diff --git a/package-lock.json b/package-lock.json index e895161b5..7e413d5c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -462,6 +462,76 @@ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz", "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" }, + "@eslint/eslintrc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", + "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.3.2", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "globals": { + "version": "13.16.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.16.0.tgz", + "integrity": "sha512-A1lrQfpNF+McdPOnnFqY3kSN0AFTy485bTi1bkLk4mVPODIUEcSfhHgRqA+QdXPksrSTTztYXx37NFV+GpGk3Q==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + } + } + }, "@ffmpeg/core": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/@ffmpeg/core/-/core-0.10.0.tgz", @@ -547,9 +617,9 @@ } }, "@fortawesome/react-fontawesome": { - "version": "0.1.18", - "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.18.tgz", - "integrity": "sha512-RwLIB4TZw0M9gvy5u+TusAA0afbwM4JQIimNH/j3ygd6aIvYPQLqXMhC9ErY26J23rDPyDZldIfPq/HpTTJ/tQ==", + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.19.tgz", + "integrity": "sha512-Hyb+lB8T18cvLNX0S3llz7PcSOAJMLwiVKBuuzwM/nI5uoBw+gQjnf9il0fR1C3DKOI5Kc79pkJ4/xB0Uw9aFQ==", "requires": { "prop-types": "^15.8.1" } @@ -580,6 +650,19 @@ "emotion": "^10.0.0", "prop-types": "^15.7.1", "react-transition-group": "^2.3.1" + }, + "dependencies": { + "react-transition-group": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", + "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", + "requires": { + "dom-helpers": "^3.4.0", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2", + "react-lifecycles-compat": "^3.0.4" + } + } } }, "@hig/theme-context": { @@ -608,6 +691,40 @@ "lodash.memoize": "^4.1.2" } }, + "@humanwhocodes/config-array": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", + "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, "@hypnosphi/create-react-context": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/@hypnosphi/create-react-context/-/create-react-context-0.3.1.tgz", @@ -1184,9 +1301,9 @@ } }, "@types/express-session": { - "version": "1.17.4", - "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.4.tgz", - "integrity": "sha512-7cNlSI8+oOBUHTfPXMwDxF/Lchx5aJ3ho7+p9jJZYVg9dVDJFh3qdMXmJtRsysnvS+C6x46k9DRYmrmCkE+MVg==", + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.5.tgz", + "integrity": "sha512-l0DhkvNVfyUPEEis8fcwbd46VptfA/jmMwHfob2TfDMf3HyPLiB9mKD71LXhz5TMUobODXPD27zXSwtFQLHm+w==", "dev": true, "requires": { "@types/express": "*" @@ -1332,6 +1449,12 @@ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, "@types/keygrip": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.2.tgz", @@ -1461,9 +1584,9 @@ "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==" }, "@types/passport": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.8.tgz", - "integrity": "sha512-Gdcvis7+7G/Mobm+25AeFi+oe5teBhHzpbCOFWeN10Bj8tnoEE1L5lkraQjzmDEKkJQuM7xSJUGIFGl/giyRfQ==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.9.tgz", + "integrity": "sha512-9+ilzUhmZQR4JP49GdC2O4UdDE3POPLwpmaTC/iLkW7l0TZCXOo1zsTnnlXPq6rP1UsUZPfbAV4IUdiwiXyC7g==", "dev": true, "requires": { "@types/express": "*" @@ -1663,10 +1786,9 @@ } }, "@types/react": { - "version": "16.14.26", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.26.tgz", - "integrity": "sha512-c/5CYyciOO4XdFcNhZW1O2woVx86k4T+DO2RorHZL7EhitkNQgSD/SgpdZJAUJa/qjVgOmTM44gHkAdZSXeQuQ==", - "dev": true, + "version": "18.0.15", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.15.tgz", + "integrity": "sha512-iz3BtLuIYH1uWdsv6wXYdhozhqj20oD4/Hk2DNXIn1kFsmp9x8d9QB6FnPhfkbhd2PgEONt9Q1x/ebkwjfFLow==", "requires": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -1676,8 +1798,7 @@ "csstype": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", - "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==", - "dev": true + "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==" } } }, @@ -1720,18 +1841,18 @@ } }, "@types/react-dom": { - "version": "16.9.16", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.16.tgz", - "integrity": "sha512-Oqc0RY4fggGA3ltEgyPLc3IV9T73IGoWjkONbsyJ3ZBn+UPPCYpU2ec0i3cEbJuEdZtkqcCF2l1zf2pBdgUGSg==", + "version": "18.0.6", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.6.tgz", + "integrity": "sha512-/5OFZgfIPSwy+YuIBP/FgJnQnsxhZhjjrnxudMddeblOouIodEQ75X14Rr4wGSG/bknL+Omy9iWlLo1u/9GzAA==", "dev": true, "requires": { - "@types/react": "^16" + "@types/react": "*" } }, "@types/react-grid-layout": { - "version": "0.17.2", - "resolved": "https://registry.npmjs.org/@types/react-grid-layout/-/react-grid-layout-0.17.2.tgz", - "integrity": "sha512-iLKQ5rI9KyPGs/BlWmeBfoikRnoOmTuddK8V5mOCgJ46e2JaecZuPhuIX1kV0mkQSk6X3XoEZALVW0XQbz9g5Q==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/react-grid-layout/-/react-grid-layout-1.3.2.tgz", + "integrity": "sha512-ZzpBEOC1JTQ7MGe1h1cPKSLP4jSWuxc+yvT4TsAlEW9+EFPzAf8nxQfFd7ea9gL17Em7PbwJZAsiwfQQBUklZQ==", "dev": true, "requires": { "@types/react": "*" @@ -1741,6 +1862,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/react-icons/-/react-icons-3.0.0.tgz", "integrity": "sha512-Vefs6LkLqF61vfV7AiAqls+vpR94q67gunhMueDznG+msAkrYgRxl7gYjNem/kZ+as2l2mNChmF1jRZzzQQtMg==", + "dev": true, "requires": { "react-icons": "*" } @@ -1758,6 +1880,7 @@ "version": "0.26.7", "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.26.7.tgz", "integrity": "sha512-mBDYl8x+oyPX/VBb3E638N0B7xG+SPk/EAMcVPeexqus/5aTpTphQi0curhhshOqRrc9t6OPoJfEUkbymse/lQ==", + "dev": true, "requires": { "@types/react": "*" }, @@ -1766,6 +1889,7 @@ "version": "18.0.12", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.12.tgz", "integrity": "sha512-duF1OTASSBQtcigUvhuiTB1Ya3OvSy+xORCiEf20H0P0lzx+/KeVsA99U5UjLXSbyo1DRJDlLKqTeM1ngosqtg==", + "dev": true, "requires": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -1775,7 +1899,8 @@ "csstype": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", - "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==" + "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==", + "dev": true } } }, @@ -1844,28 +1969,11 @@ } }, "@types/react-transition-group": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.4.tgz", - "integrity": "sha512-7gAPz7anVK5xzbeQW9wFBDg7G++aPLAFY0QaSMOou9rJZpbuI58WAuJrgu+qR92l61grlnCUe7AFX8KGahAgug==", + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==", "requires": { "@types/react": "*" - }, - "dependencies": { - "@types/react": { - "version": "18.0.12", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.12.tgz", - "integrity": "sha512-duF1OTASSBQtcigUvhuiTB1Ya3OvSy+xORCiEf20H0P0lzx+/KeVsA99U5UjLXSbyo1DRJDlLKqTeM1ngosqtg==", - "requires": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "csstype": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", - "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==" - } } }, "@types/reactcss": { @@ -2359,22 +2467,22 @@ } }, "@webpack-cli/configtest": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.1.1.tgz", - "integrity": "sha512-1FBc1f9G4P/AxMqIgfZgeOTuRnwZMten8E7zap5zgpPInnCrP8D4Q81+4CWIch8i/Nf7nXjP0v6CjjbHOrXhKg==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", + "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==" }, "@webpack-cli/info": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.4.1.tgz", - "integrity": "sha512-PKVGmazEq3oAo46Q63tpMr4HipI3OPfP7LiNOEJg963RMgT0rqheag28NCML0o3GIzA3DmxP1ZIAv9oTX1CUIA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz", + "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==", "requires": { "envinfo": "^7.7.3" } }, "@webpack-cli/serve": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.6.1.tgz", - "integrity": "sha512-gNGTiTrjEVQ0OcVnzsRSqTxaBSr+dmTfm+qJsCDluky8uhdLWep7Gcr62QsAKHTMxjCS/8nEITsmFAhfIx+QSw==" + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", + "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==" }, "@webscopeio/react-textarea-autocomplete": { "version": "4.9.1", @@ -2487,7 +2595,7 @@ "after": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", - "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" + "integrity": "sha512-QbJ0NTQ/I9DI3uSJA4cbexiwQeRAfjPScqIbSjUDd9TOrcg6pTkdgziesOqxBMBzit8vFCTwrP27t13vFOORRA==" }, "agent-base": { "version": "6.0.2", @@ -2617,6 +2725,23 @@ } } }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + }, + "dependencies": { + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + } + } + }, "ansi-html-community": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", @@ -2735,6 +2860,16 @@ "sprintf-js": "~1.0.2" } }, + "aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" + } + }, "arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", @@ -2852,6 +2987,19 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, + "array-includes": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", + "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.7" + } + }, "array-union": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", @@ -2872,6 +3020,30 @@ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" }, + "array.prototype.flat": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz", + "integrity": "sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.2", + "es-shim-unscopables": "^1.0.0" + } + }, + "array.prototype.flatmap": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz", + "integrity": "sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.2", + "es-shim-unscopables": "^1.0.0" + } + }, "array.prototype.reduce": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.4.tgz", @@ -2922,6 +3094,18 @@ "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" }, + "ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", + "dev": true + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, "async": { "version": "2.6.4", "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", @@ -3006,6 +3190,12 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" }, + "axe-core": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.4.2.tgz", + "integrity": "sha512-LVAaGp/wkkgYJcjmHsoKx4juT1aQvJyPcW09MLCjVTh3V2cc6PnyempiLMNH5iMdfIX/zdbjUx2KDjMLCTdPeA==", + "dev": true + }, "axios": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", @@ -3014,6 +3204,12 @@ "follow-redirects": "1.5.10" } }, + "axobject-query": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", + "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==", + "dev": true + }, "babel": { "version": "6.23.0", "resolved": "https://registry.npmjs.org/babel/-/babel-6.23.0.tgz", @@ -3078,6 +3274,28 @@ } } }, + "babel-eslint": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", + "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.7.0", + "@babel/traverse": "^7.7.0", + "@babel/types": "^7.7.0", + "eslint-visitor-keys": "^1.0.0", + "resolve": "^1.12.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, "babel-plugin-emotion": { "version": "10.2.2", "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.2.2.tgz", @@ -3169,7 +3387,7 @@ "backo2": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" + "integrity": "sha512-zj6Z6M7Eq+PBZ7PQxl5NT665MvJdAkzp0f60nAJ+sLaSCBPMwVak5ZegFbgVCzFcCJTKFoMizvM5Ld7+JrRJHA==" }, "balanced-match": { "version": "1.0.2", @@ -3234,7 +3452,7 @@ "base64-arraybuffer": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz", - "integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=" + "integrity": "sha512-a1eIFi4R9ySrbiMuyTGx5e92uRH5tQY6kArNcFaKBUleIoLjdjBg7Zxm3Mqm3Kmkf27HLR/1fnxX9q8GQ7Iavg==" }, "base64-js": { "version": "1.5.1", @@ -3752,9 +3970,9 @@ "integrity": "sha512-u+Ll+RDaiQEproTQjZLjZwyfNgNezA1fERMT7/54npcz+PkbVJUAHXMUz4bkXQYRPWrcFNO0Fbi1mwjfXg6N5g==" }, "canvas": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.9.1.tgz", - "integrity": "sha512-vSQti1uG/2gjv3x6QLOZw7TctfufaerTWbVe+NSduHxxLGB+qf3kFgQ6n66DSnuoINtVUjrLLIK2R+lxrBG07A==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.9.3.tgz", + "integrity": "sha512-WOUM7ghii5TV2rbhaZkh1youv/vW1/Canev6Yx6BG2W+1S07w8jKZqKkPnbiPpQEDsnJdN8ouDd7OvQEGXDcUw==", "requires": { "@mapbox/node-pre-gyp": "^1.0.0", "nan": "^2.15.0", @@ -3822,6 +4040,12 @@ "is-regex": "^1.0.3" } }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, "check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", @@ -3932,6 +4156,21 @@ "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=" }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true + }, "clipboard": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-1.7.1.tgz", @@ -4140,7 +4379,7 @@ "component-bind": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", - "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" + "integrity": "sha512-WZveuKPeKAG9qY+FkYDeADzdHyTYdIboXS59ixDeRJL5ZhxpqUnxSOwop4FQjMsiYm3/Or8cegVbpAHNA7pHxw==" }, "component-emitter": { "version": "1.3.0", @@ -4150,7 +4389,7 @@ "component-inherit": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", - "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=" + "integrity": "sha512-w+LhYREhatpVqTESyGFg3NlP6Iu0kEKUHETY9GoZP/pQyW4mHFZuFWRUCIqVPZ36ueVLtoOEZaAqbCF2RDndaA==" }, "compress-brotli": { "version": "1.3.8", @@ -4295,6 +4534,12 @@ } } }, + "confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "dev": true + }, "connect-flash": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/connect-flash/-/connect-flash-0.1.1.tgz", @@ -4860,16 +5105,6 @@ "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", "dev": true }, - "d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, - "requires": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, "d3-array": { "version": "2.12.1", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", @@ -4995,6 +5230,12 @@ "d3-transition": "2" } }, + "damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -5584,6 +5825,15 @@ "buffer-indexof": "^1.0.0" } }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, "doctypes": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", @@ -5809,9 +6059,9 @@ } }, "engine.io": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.5.0.tgz", - "integrity": "sha512-21HlvPUKaitDGE4GXNtQ7PLP0Sz4aWLddMPw2VTyFz1FVZqu/kZsJUO8WNpKuE/OCL7nkfRaOui2ZCJloGznGA==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.6.0.tgz", + "integrity": "sha512-Kc8fo5bbg8F4a2f3HPHTEpGyq/IRIQpyeHu3H1ThR14XDD7VrLcsGBo16HUpahgp8YkHJDaU5gNxJZbuGcuueg==", "requires": { "accepts": "~1.3.4", "base64id": "2.0.0", @@ -5865,7 +6115,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "ws": { "version": "7.4.6", @@ -5985,6 +6235,15 @@ "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", "dev": true }, + "es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, "es-to-primitive": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", @@ -5995,28 +6254,6 @@ "is-symbol": "^1.0.2" } }, - "es5-ext": { - "version": "0.10.61", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.61.tgz", - "integrity": "sha512-yFhIqQAzu2Ca2I4SE2Au3rxVfmohU9Y7wqGR+s7+H7krk26NXhIRAZDgqd6xqjCEFUomDEA3/Bo/7fKmIkW1kA==", - "dev": true, - "requires": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "next-tick": "^1.1.0" - } - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, "es6-promise": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz", @@ -6028,7 +6265,6 @@ "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", "dev": true, "requires": { - "d": "^1.0.1", "ext": "^1.1.2" } }, @@ -6069,6 +6305,1111 @@ } } }, + "eslint": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.19.0.tgz", + "integrity": "sha512-SXOPj3x9VKvPe81TjjUJCYlV4oJjQw68Uek+AM0X4p+33dj2HY5bpTZOgnQHcG2eAm1mtCU9uNMnJi7exU/kYw==", + "dev": true, + "requires": { + "@eslint/eslintrc": "^1.3.0", + "@humanwhocodes/config-array": "^0.9.2", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.2", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^6.0.1", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "globals": { + "version": "13.16.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.16.0.tgz", + "integrity": "sha512-A1lrQfpNF+McdPOnnFqY3kSN0AFTy485bTi1bkLk4mVPODIUEcSfhHgRqA+QdXPksrSTTztYXx37NFV+GpGk3Q==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "eslint-config-airbnb": { + "version": "19.0.4", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz", + "integrity": "sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==", + "dev": true, + "requires": { + "eslint-config-airbnb-base": "^15.0.0", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5" + }, + "dependencies": { + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + } + } + }, + "eslint-config-airbnb-base": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", + "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "dev": true, + "requires": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + }, + "dependencies": { + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "eslint-config-esnext": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-esnext/-/eslint-config-esnext-4.1.0.tgz", + "integrity": "sha512-GhfVEXdqYKEIIj7j+Fw2SQdL9qyZMekgXfq6PyXM66cQw0B435ddjz3P3kxOBVihMRJ0xGYjosaveQz5Y6z0uA==", + "dev": true, + "requires": { + "babel-eslint": "^10.0.1", + "eslint": "^6.8.0", + "eslint-plugin-babel": "^5.2.1", + "eslint-plugin-import": "^2.14.0" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "eslint": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", + "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.3", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.2", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^7.0.0", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.14", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.3", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + } + }, + "eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + }, + "espree": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", + "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.1.0" + } + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + } + }, + "flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } + } + }, + "eslint-config-node": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-node/-/eslint-config-node-4.1.0.tgz", + "integrity": "sha512-Wz17xV5O2WFG8fGdMYEBdbiL6TL7YNJSJvSX9V4sXQownewfYmoqlly7wxqLkOUv/57pq6LnnotMiQQrrPjCqQ==", + "dev": true, + "requires": { + "eslint": "^6.8.0", + "eslint-config-esnext": "^4.1.0" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "eslint": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", + "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.3", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.2", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^7.0.0", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.14", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.3", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + } + }, + "eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + }, + "espree": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", + "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.1.0" + } + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + } + }, + "flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } + } + }, + "eslint-config-prettier": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", + "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "dev": true + }, + "eslint-import-resolver-node": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", + "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "resolve": "^1.20.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-module-utils": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz", + "integrity": "sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "find-up": "^2.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "dev": true + } + } + }, + "eslint-plugin-babel": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-babel/-/eslint-plugin-babel-5.3.1.tgz", + "integrity": "sha512-VsQEr6NH3dj664+EyxJwO4FCYm/00JhYb3Sk3ft8o+fpKuIfQ9TaW6uVUfvwMXHcf/lsnRIoyFPsLMyiWCSL/g==", + "dev": true, + "requires": { + "eslint-rule-composer": "^0.3.0" + } + }, + "eslint-plugin-es": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", + "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", + "dev": true, + "requires": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "dependencies": { + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "eslint-plugin-import": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", + "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", + "dev": true, + "requires": { + "array-includes": "^3.1.4", + "array.prototype.flat": "^1.2.5", + "debug": "^2.6.9", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-module-utils": "^2.7.3", + "has": "^1.0.3", + "is-core-module": "^2.8.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.5", + "resolve": "^1.22.0", + "tsconfig-paths": "^3.14.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "eslint-plugin-jsx-a11y": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.6.0.tgz", + "integrity": "sha512-kTeLuIzpNhXL2CwLlc8AHI0aFRwWHcg483yepO9VQiHzM9bZwJdzTkzBszbuPrbgGmq2rlX/FaT2fJQsjUSHsw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.18.3", + "aria-query": "^4.2.2", + "array-includes": "^3.1.5", + "ast-types-flow": "^0.0.7", + "axe-core": "^4.4.2", + "axobject-query": "^2.2.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "has": "^1.0.3", + "jsx-ast-utils": "^3.3.1", + "language-tags": "^1.0.5", + "minimatch": "^3.1.2", + "semver": "^6.3.0" + }, + "dependencies": { + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "eslint-plugin-node": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", + "dev": true, + "requires": { + "eslint-plugin-es": "^3.0.0", + "eslint-utils": "^2.0.0", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" + }, + "dependencies": { + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "eslint-plugin-prettier": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", + "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, + "eslint-plugin-react": { + "version": "7.30.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.30.1.tgz", + "integrity": "sha512-NbEvI9jtqO46yJA3wcRF9Mo0lF9T/jhdHqhCHXiXtD+Zcb98812wvokjWpU7Q4QH5edo6dmqrukxVvWWXHlsUg==", + "dev": true, + "requires": { + "array-includes": "^3.1.5", + "array.prototype.flatmap": "^1.3.0", + "doctrine": "^2.1.0", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.5", + "object.fromentries": "^2.0.5", + "object.hasown": "^1.1.1", + "object.values": "^1.1.5", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.3", + "semver": "^6.3.0", + "string.prototype.matchall": "^4.0.7" + }, + "dependencies": { + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "resolve": { + "version": "2.0.0-next.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", + "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true + }, + "eslint-rule-composer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz", + "integrity": "sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==", + "dev": true + }, "eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -6079,11 +7420,62 @@ "estraverse": "^4.1.1" } }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true + }, + "espree": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz", + "integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==", + "dev": true, + "requires": { + "acorn": "^8.7.1", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + } + }, "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, "esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", @@ -6451,6 +7843,28 @@ } } }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } + } + }, "extglob": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", @@ -6554,6 +7968,12 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -6614,6 +8034,24 @@ "when": ">= 0.0.1" } }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, "file-loader": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-3.0.1.tgz", @@ -6818,6 +8256,22 @@ "is-buffer": "~2.0.3" } }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz", + "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==", + "dev": true + }, "flexlayout-react": { "version": "0.3.11", "resolved": "https://registry.npmjs.org/flexlayout-react/-/flexlayout-react-0.3.11.tgz", @@ -7144,6 +8598,12 @@ "functions-have-names": "^1.2.2" } }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true + }, "functions-have-names": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", @@ -7645,14 +9105,14 @@ "isarray": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + "integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ==" } } }, "has-cors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", - "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" + "integrity": "sha512-g5VNKdkFuUuVCP9gYfDJHjK2nqdQJ7aDLTnycnc2+RvsOQbuLdF5pm7vuE5J76SEBIQjs4kQY/BWq74JUmjbXA==" }, "has-flag": { "version": "3.0.0", @@ -7963,11 +9423,6 @@ } } }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==" - }, "hyntax": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/hyntax/-/hyntax-1.1.9.tgz", @@ -8153,7 +9608,7 @@ "indexof": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" + "integrity": "sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg==" }, "inflight": { "version": "1.0.6", @@ -8198,6 +9653,116 @@ "css-in-js-utils": "^2.0.0" } }, + "inquirer": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", + "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.19", + "mute-stream": "0.0.8", + "run-async": "^2.4.0", + "rxjs": "^6.6.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "inspect-function": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/inspect-function/-/inspect-function-0.2.2.tgz", @@ -9055,6 +10620,12 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -9195,6 +10766,30 @@ "promise": "^7.0.1" } }, + "jsx-ast-utils": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.2.tgz", + "integrity": "sha512-4ZCADZHRkno244xlNnn4AOG6sRQ7iBZ5BbgZ4vW4y5IZw7cVUD1PPeblm1xx/nfmMxPdt/LHsXZW8z/j58+l9Q==", + "dev": true, + "requires": { + "array-includes": "^3.1.5", + "object.assign": "^4.1.2" + }, + "dependencies": { + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + } + } + }, "jszip": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.0.tgz", @@ -9292,6 +10887,21 @@ "graceful-fs": "^4.1.9" } }, + "language-subtag-registry": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", + "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==", + "dev": true + }, + "language-tags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", + "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==", + "dev": true, + "requires": { + "language-subtag-registry": "~0.3.2" + } + }, "latest-version": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", @@ -9460,7 +11070,7 @@ "lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" }, "lodash.isplainobject": { "version": "4.0.6", @@ -9711,7 +11321,7 @@ "mathquill": { "version": "0.10.1-a", "resolved": "https://registry.npmjs.org/mathquill/-/mathquill-0.10.1-a.tgz", - "integrity": "sha1-vyylaQEAY6w0vNXVKa3Ag3zVPD8=", + "integrity": "sha512-snSAEwAtwdwBFSor+nVBnWWQtTw67kgAgKMyAIxuz4ZPboy0qkWZmd7BL3lfOXp/INihhRlU1PcfaAtDaRhmzA==", "requires": { "jquery": "^1.12.3" }, @@ -9719,7 +11329,7 @@ "jquery": { "version": "1.12.4", "resolved": "https://registry.npmjs.org/jquery/-/jquery-1.12.4.tgz", - "integrity": "sha1-AeHfuikP5z3rp3zurLD5ui/sngw=" + "integrity": "sha512-UEVp7PPK9xXYSk8xqXCJrkXnKZtlgWkd2GsAQbMRFK6S/ePU2JN5G2Zum8hIVjzR3CpdfSqdqAzId/xd4TJHeg==" } } }, @@ -9817,7 +11427,8 @@ "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true }, "methods": { "version": "1.1.2", @@ -9871,7 +11482,8 @@ "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true }, "mimic-response": { "version": "2.1.0", @@ -10301,6 +11913,12 @@ "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", "dev": true }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, "nan": { "version": "2.16.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.16.0.tgz", @@ -10339,6 +11957,12 @@ "resolved": "https://registry.npmjs.org/native-or-bluebird/-/native-or-bluebird-1.2.0.tgz", "integrity": "sha1-OcR7/Xgl0fuf+tMiEK4l2q3xAck=" }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, "negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -14468,6 +16092,28 @@ "object-keys": "^1.0.11" } }, + "object.entries": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz", + "integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + } + }, + "object.fromentries": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.5.tgz", + "integrity": "sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + } + }, "object.getownpropertydescriptors": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.4.tgz", @@ -14479,6 +16125,16 @@ "es-abstract": "^1.20.1" } }, + "object.hasown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.1.tgz", + "integrity": "sha512-LYLe4tivNQzq4JdaWW6WO3HMZZJWzkkH8fnI6EebWl0VZth2wL2Lovm74ep2/gZzlaTdV62JZHEqHQ2yVn8Q/A==", + "dev": true, + "requires": { + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + } + }, "object.pick": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", @@ -14487,6 +16143,17 @@ "isobject": "^3.0.1" } }, + "object.values": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", + "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + } + }, "obuf": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", @@ -14518,6 +16185,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, "requires": { "mimic-fn": "^2.1.0" } @@ -14563,9 +16231,9 @@ } }, "orderedmap": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-1.1.8.tgz", - "integrity": "sha512-eWEYOAggZZpZbJ9CTsqAKOTxlbBHdHZ8pzcfEvNTxGrjQ/m+Q25nSWUiMlT9MTbgpB6FOiBDKqsgJ2FlLDVNaw==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.0.0.tgz", + "integrity": "sha512-buf4PoAMlh45b8a8gsGy/X6w279TSqkyAS0C0wdTSJwFSU+ljQFJON5I8NfjLHoCXwpSROIo2wr0g33T+kQshQ==" }, "os-homedir": { "version": "1.0.2", @@ -15270,6 +16938,21 @@ "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" }, + "prettier": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "dev": true + }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, "pretty-error": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", @@ -15402,11 +17085,11 @@ } }, "prosemirror-model": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.18.0.tgz", - "integrity": "sha512-EZLl0mvVIixEL1V538GsPP4Iw+D83gBRdLcjL7VouzoPPWlhhv/0Y0V9ttT5lUhF7jieIrNHOa3XNFs1FwgrOw==", + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.18.1.tgz", + "integrity": "sha512-IxSVBKAEMjD7s3n8cgtwMlxAXZrC7Mlag7zYsAKDndAqnDScvSmp/UdnRTV/B33lTCVU3CCm7dyAn/rVVD0mcw==", "requires": { - "orderedmap": "^1.1.0" + "orderedmap": "^2.0.0" } }, "prosemirror-schema-list": { @@ -15420,9 +17103,9 @@ } }, "prosemirror-state": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.0.tgz", - "integrity": "sha512-mVDZdjNX/YT5FvypiwbphJe9psA5h+j9apsSszVRFc6oKFoIInvzdujh8QW9f9lwHtSYajLxNiM1hPhd0Sl1XA==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.1.tgz", + "integrity": "sha512-U/LBDW2gNmVa07sz/D229XigSdDQ5CLFwVB1Vb32MJbAHHhWe/6pOc721faI17tqw4pZ49i1xfY/jEZ9tbIhPg==", "requires": { "prosemirror-model": "^1.0.0", "prosemirror-transform": "^1.0.0" @@ -15437,9 +17120,9 @@ } }, "prosemirror-view": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.26.0.tgz", - "integrity": "sha512-tT8ICUAw6YfOTRIwVQJoCdF65BYxoWFjAqmv44Kn4phYjdLJ8Vc92dhL3LnmwAQuZ9yfPHzRR6Lfb3/5EVJHWg==", + "version": "1.26.5", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.26.5.tgz", + "integrity": "sha512-SO+AX6WwdbJZHVvuloXI0qfO+YJAnZAat8qrYwfiqTQwL/FewLUnr0m3EXZ6a60hQs8/Q/lzeJXiFR/dOPaaKQ==", "requires": { "prosemirror-model": "^1.16.0", "prosemirror-state": "^1.0.0", @@ -15981,15 +17664,26 @@ } }, "react-grid-layout": { - "version": "0.18.3", - "resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-0.18.3.tgz", - "integrity": "sha512-lHkrk941Tk5nTwZPa9uj6ttHBT0VehSHwEhWbINBJKvM1GRaFNOefvjcuxSyuCI5JWjVUP+Qm3ARt2470AlxMA==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-1.3.4.tgz", + "integrity": "sha512-sB3rNhorW77HUdOjB4JkelZTdJGQKuXLl3gNg+BI8gJkTScspL1myfZzW/EM0dLEn+1eH+xW+wNqk0oIM9o7cw==", "requires": { - "classnames": "2.x", + "clsx": "^1.1.1", "lodash.isequal": "^4.0.0", - "prop-types": "^15.0.0", + "prop-types": "^15.8.1", "react-draggable": "^4.0.0", - "react-resizable": "^1.9.0" + "react-resizable": "^3.0.4" + }, + "dependencies": { + "react-resizable": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-3.0.4.tgz", + "integrity": "sha512-StnwmiESiamNzdRHbSSvA65b0ZQJ7eVQpPusrSmcpyGKzC0gojhtO62xxH6YOBmepk9dQTBi9yxidL3W4s3EBA==", + "requires": { + "prop-types": "15.x", + "react-draggable": "^4.0.3" + } + } } }, "react-icons": { @@ -16189,9 +17883,9 @@ } }, "react-refresh-typescript": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/react-refresh-typescript/-/react-refresh-typescript-2.0.5.tgz", - "integrity": "sha512-TlcxF9VMBBS8dHvMD+3dklgArXeIhjbtT7R09ycsXvhPPYmv70J67kiGT8mxe6/tYzkql9aCHaZAY6QYdutLdw==" + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/react-refresh-typescript/-/react-refresh-typescript-2.0.7.tgz", + "integrity": "sha512-KbuW57FauO11e6a9gU836sCm3M3z0b2+J2qPhUudg0QplOfz0eAF3gMeshcUC/ChfNLJCK1SZxvYmUtRxiZE5A==" }, "react-resizable": { "version": "1.11.1", @@ -16298,14 +17992,30 @@ "integrity": "sha512-EblIqTAsIpkYeM8bZtC4lcpTE0A2zCEGipFB52RgcQq/q+0oryrk7Sxt+sqhIjUu6xMNEVywV8dr74lz5yWO6A==" }, "react-transition-group": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", - "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", + "integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==", "requires": { - "dom-helpers": "^3.4.0", + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", "loose-envify": "^1.4.0", - "prop-types": "^15.6.2", - "react-lifecycles-compat": "^3.0.4" + "prop-types": "^15.6.2" + }, + "dependencies": { + "csstype": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", + "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==" + }, + "dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "requires": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + } } }, "react-use-measure": { @@ -16492,6 +18202,12 @@ "functions-have-names": "^1.2.2" } }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, "registry-auth-token": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", @@ -16811,6 +18527,16 @@ } } }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, "ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", @@ -16856,6 +18582,12 @@ "sdp": "^2.6.0" } }, + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true + }, "run-queue": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", @@ -16873,6 +18605,23 @@ } } }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -17446,6 +19195,17 @@ "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", "dev": true }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + } + }, "sliced": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", @@ -17567,15 +19327,15 @@ } }, "socket.io": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.4.1.tgz", - "integrity": "sha512-Si18v0mMXGAqLqCVpTxBa8MGqriHGQh8ccEOhmsmNS3thNCGBwO8WGrwMibANsWtQQ5NStdZwHqZR3naJVFc3w==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.5.0.tgz", + "integrity": "sha512-gGunfS0od3VpwDBpGwVkzSZx6Aqo9uOcf1afJj2cKnKFAoyl16fvhpsUhmUFd4Ldbvl5JvRQed6eQw6oQp6n8w==", "requires": { "debug": "~4.1.0", - "engine.io": "~3.5.0", + "engine.io": "~3.6.0", "has-binary2": "~1.0.2", "socket.io-adapter": "~1.1.0", - "socket.io-client": "2.4.0", + "socket.io-client": "2.5.0", "socket.io-parser": "~3.4.0" }, "dependencies": { @@ -17595,9 +19355,9 @@ "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==" }, "socket.io-client": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.4.0.tgz", - "integrity": "sha512-M6xhnKQHuuZd4Ba9vltCLT9oa+YvTsP8j9NcEiLElfIg8KeYPyhWOes6x4t+LTAC8enQbE/995AdTem2uNyKKQ==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.5.0.tgz", + "integrity": "sha512-lOO9clmdgssDykiOmVQQitwBAF3I6mYcQAo7hQ7AM6Ny5X7fp8hIJ3HcQs3Rjz4SoggoxA1OgrQyY8EgTbcPYw==", "requires": { "backo2": "1.0.2", "component-bind": "1.0.0", @@ -17623,12 +19383,12 @@ "isarray": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + "integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ==" }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "socket.io-parser": { "version": "3.3.2", @@ -18052,6 +19812,22 @@ "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz", "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==" }, + "string.prototype.matchall": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz", + "integrity": "sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1", + "get-intrinsic": "^1.1.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.4.1", + "side-channel": "^1.0.4" + } + }, "string.prototype.trimend": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", @@ -18128,11 +19904,6 @@ "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==" - }, "strip-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", @@ -18240,6 +20011,46 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, "table-layout": { "version": "0.4.5", "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-0.4.5.tgz", @@ -18373,6 +20184,12 @@ } } }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, "textarea-caret": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/textarea-caret/-/textarea-caret-3.1.0.tgz", @@ -18451,10 +20268,19 @@ "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.2.tgz", "integrity": "sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==" }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, "to-array": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", - "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=" + "integrity": "sha512-LhVdShQD/4Mk4zXNroIQZJC+Ap3zgLcDuwEdcmLv9CCO73NWockQDwyUnW/m8VX/EElfL6FcYx7EeutN4HJA6A==" }, "to-fast-properties": { "version": "2.0.0", @@ -18835,6 +20661,26 @@ } } }, + "tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true + } + } + }, "tslib": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", @@ -18946,12 +20792,6 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, - "type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "dev": true - }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", @@ -18966,6 +20806,12 @@ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -18986,9 +20832,9 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, "typescript": { - "version": "4.7.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.3.tgz", - "integrity": "sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA==", + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", "dev": true }, "typescript-collections": { @@ -19415,6 +21261,12 @@ "resolved": "https://registry.npmjs.org/uuid-js/-/uuid-js-0.7.5.tgz", "integrity": "sha1-bIhtAqU9LUDc8l2RoXC0p7JblNA=" }, + "v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, "valid-url": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz", @@ -19637,17 +21489,17 @@ } }, "webpack-cli": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.9.2.tgz", - "integrity": "sha512-m3/AACnBBzK/kMTcxWHcZFPrw/eQuY4Df1TxvIWfWM2x7mRqBQCqKEd96oCUa9jkapLBaFfRce33eGDb4Pr7YQ==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", + "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", "requires": { "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.1.1", - "@webpack-cli/info": "^1.4.1", - "@webpack-cli/serve": "^1.6.1", + "@webpack-cli/configtest": "^1.2.0", + "@webpack-cli/info": "^1.5.0", + "@webpack-cli/serve": "^1.7.0", "colorette": "^2.0.14", "commander": "^7.0.0", - "execa": "^5.0.0", + "cross-spawn": "^7.0.3", "fastest-levenshtein": "^1.0.12", "import-local": "^3.0.2", "interpret": "^2.2.0", @@ -19670,40 +21522,11 @@ "which": "^2.0.1" } }, - "execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, "interpret": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==" }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "requires": { - "path-key": "^3.0.0" - } - }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -20281,6 +22104,15 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, "write-file-atomic": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", @@ -20445,7 +22277,7 @@ "yeast": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", - "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" + "integrity": "sha512-8HFIh676uyGYP6wP13R/j6OJ/1HwJ46snpvzE7aHAN3Ryqh2yX6Xox2B4CUmTwwOIzlG3Bs7ocsP5dZH/R1Qbg==" }, "yn": { "version": "2.0.0", diff --git a/package.json b/package.json index e4ec2798d..93968dce4 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "@types/exif": "^0.6.3", "@types/express": "^4.17.13", "@types/express-flash": "0.0.0", - "@types/express-session": "^1.17.4", + "@types/express-session": "^1.17.5", "@types/express-validator": "^3.0.0", "@types/file-saver": "^2.0.5", "@types/google-maps-react": "^2.0.5", @@ -49,7 +49,7 @@ "@types/mongoose": "^5.11.97", "@types/node": "^10.17.60", "@types/nodemailer": "^4.6.6", - "@types/passport": "^1.0.7", + "@types/passport": "^1.0.9", "@types/passport-google-oauth20": "^2.0.11", "@types/passport-local": "^1.0.34", "@types/pdfjs-dist": "^2.10.378", @@ -65,12 +65,15 @@ "@types/prosemirror-transform": "^1.1.5", "@types/prosemirror-view": "^1.23.1", "@types/rc-switch": "^1.9.2", - "@types/react": "^16.14.23", + "@types/react": "^18.0.15", + "@types/react-icons": "^3.0.0", + "@types/react-reconciler": "^0.26.4", + "@types/react-transition-group": "^4.4.5", "@types/react-autosuggest": "^9.3.14", "@types/react-color": "^2.17.6", "@types/react-datepicker": "^3.1.8", - "@types/react-dom": "^16.9.14", - "@types/react-grid-layout": "^0.17.2", + "@types/react-dom": "^18.0.6", + "@types/react-grid-layout": "^1.3.2", "@types/react-measure": "^2.0.8", "@types/react-select": "^3.1.2", "@types/react-table": "^6.8.9", @@ -117,7 +120,7 @@ "ts-node-dev": "^1.1.8", "tslint": "^5.20.1", "tslint-loader": "^3.6.0", - "typescript": "^4.6.2", + "typescript": "^4.7.4", "webpack": "^5.69.1", "webpack-dev-server": "^3.11.3", "webpack-hot-middleware": "^2.25.1" @@ -129,7 +132,7 @@ "@fortawesome/free-brands-svg-icons": "^5.15.4", "@fortawesome/free-regular-svg-icons": "^5.15.4", "@fortawesome/free-solid-svg-icons": "^5.15.4", - "@fortawesome/react-fontawesome": "^0.1.17", + "@fortawesome/react-fontawesome": "^0.1.19", "@hig/flyout": "^1.3.1", "@hig/theme-context": "^2.1.3", "@hig/theme-data": "^2.23.1", @@ -145,14 +148,13 @@ "@types/dom-speech-recognition": "0.0.1", "@types/formidable": "1.0.31", "@types/google-maps": "^3.2.3", - "@types/react-icons": "^3.0.0", - "@types/react-reconciler": "^0.26.4", "@types/reveal": "^3.3.33", "@types/supercluster": "^7.1.0", "@types/three": "^0.126.2", "@types/web": "0.0.53", "@types/webscopeio__react-textarea-autocomplete": "^4.7.2", "@webscopeio/react-textarea-autocomplete": "^4.9.1", + "D": "^1.0.0", "adm-zip": "^0.4.16", "archiver": "^3.1.1", "array-batcher": "^1.2.3", @@ -167,7 +169,7 @@ "bootstrap": "^4.6.1", "browser-assert": "^1.2.1", "bson": "^4.6.1", - "canvas": "^2.9.0", + "canvas": "^2.9.3", "child_process": "^1.0.2", "chrome": "^0.1.0", "class-transformer": "^0.2.0", @@ -178,7 +180,6 @@ "cookie-parser": "^1.4.6", "cookie-session": "^2.0.0", "cors": "^2.8.5", - "D": "^1.0.0", "depcheck": "^0.9.2", "equation-editor-react": "github:bobzel/equation-editor-react#useLocally", "exif": "^0.6.0", @@ -250,11 +251,11 @@ "prosemirror-history": "^1.2.0", "prosemirror-inputrules": "^1.1.3", "prosemirror-keymap": "^1.1.5", - "prosemirror-model": "^1.16.1", + "prosemirror-model": "^1.18.1", "prosemirror-schema-list": "^1.1.6", - "prosemirror-state": "^1.3.4", + "prosemirror-state": "^1.4.1", "prosemirror-transform": "^1.3.4", - "prosemirror-view": "^1.23.7", + "prosemirror-view": "^1.26.5", "pug": "^2.0.4", "puppeteer": "^3.3.0", "query-string": "^6.14.1", @@ -268,18 +269,19 @@ "react-compound-slider": "^2.5.0", "react-datepicker": "^3.8.0", "react-dom": "^16.14.0", - "react-grid-layout": "^0.18.3", + "react-grid-layout": "^1.3.4", "react-icons": "^4.3.1", "react-image-lightbox-with-rotate": "^5.1.1", "react-jsx-parser": "^1.29.0", "react-loading": "^2.0.3", "react-measure": "^2.5.2", - "react-refresh-typescript": "^2.0.3", + "react-refresh-typescript": "^2.0.7", "react-resizable": "^1.11.1", "react-resizable-rotatable-draggable": "^0.2.0", "react-reveal": "^1.2.2", "react-select": "^3.2.0", "react-table": "^6.11.5", + "react-transition-group": "^4.4.2", "readline": "^1.3.0", "request": "^2.88.2", "request-promise": "^4.2.6", @@ -288,8 +290,8 @@ "serializr": "^1.5.4", "sharp": "^0.23.4", "shelljs": "^0.8.5", - "socket.io": "^2.4.1", - "socket.io-client": "^2.4.0", + "socket.io": "^2.5.0", + "socket.io-client": "^2.5.0", "solr-node": "^1.2.1", "standard-http-error": "^2.0.1", "stream-browserify": "^3.0.0", @@ -306,7 +308,7 @@ "uuid": "^3.4.0", "valid-url": "^1.0.9", "web-request": "^1.0.7", - "webpack-cli": "^4.9.2", + "webpack-cli": "^4.10.0", "webpack-dev-middleware": "^5.3.1", "webrtc-adapter": "^7.7.1", "wikijs": "^6.3.3", diff --git a/src/client/util/CaptureManager.tsx b/src/client/util/CaptureManager.tsx index c247afa26..0b5957fac 100644 --- a/src/client/util/CaptureManager.tsx +++ b/src/client/util/CaptureManager.tsx @@ -1,17 +1,17 @@ -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, observable, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import * as React from "react"; -import { convertToObject } from "typescript"; -import { Doc, DocListCast } from "../../fields/Doc"; -import { BoolCast, StrCast, Cast } from "../../fields/Types"; -import { addStyleSheet, addStyleSheetRule, Utils } from "../../Utils"; -import { LightboxView } from "../views/LightboxView"; -import { MainViewModal } from "../views/MainViewModal"; -import "./CaptureManager.scss"; -import { SelectionManager } from "./SelectionManager"; -import { undoBatch } from "./UndoManager"; -const higflyout = require("@hig/flyout"); +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, computed, observable, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { convertToObject } from 'typescript'; +import { Doc, DocListCast } from '../../fields/Doc'; +import { BoolCast, StrCast, Cast } from '../../fields/Types'; +import { addStyleSheet, addStyleSheetRule, Utils } from '../../Utils'; +import { LightboxView } from '../views/LightboxView'; +import { MainViewModal } from '../views/MainViewModal'; +import './CaptureManager.scss'; +import { SelectionManager } from './SelectionManager'; +import { undoBatch } from './UndoManager'; +const higflyout = require('@hig/flyout'); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -22,32 +22,31 @@ export class CaptureManager extends React.Component<{}> { @observable _document: any; @observable isOpen: boolean = false; // whether the CaptureManager is to be displayed or not. - constructor(props: {}) { super(props); CaptureManager.Instance = this; } - public close = action(() => this.isOpen = false); + public close = action(() => (this.isOpen = false)); public open = action((doc: Doc) => { this.isOpen = true; this._document = doc; }); - @computed get visibilityContent() { - - return <div className="capture-block"> - <div className="capture-block-title">Visibility</div> - <div className="capture-block-radio"> - <div className="radio-container"> - <input type="radio" value="private" name="visibility" style={{ margin: 0, marginRight: 5 }} /> Private - </div> - <div className="radio-container"> - <input type="radio" value="public" name="visibility" style={{ margin: 0, marginRight: 5 }} /> Public + return ( + <div className="capture-block"> + <div className="capture-block-title">Visibility</div> + <div className="capture-block-radio"> + <div className="radio-container"> + <input type="radio" value="private" name="visibility" style={{ margin: 0, marginRight: 5 }} /> Private + </div> + <div className="radio-container"> + <input type="radio" value="public" name="visibility" style={{ margin: 0, marginRight: 5 }} /> Public + </div> </div> </div> - </div>; + ); } @computed get linksContent() { @@ -66,75 +65,79 @@ export class CaptureManager extends React.Component<{}> { order.push( <div className="list-item"> <div className="number">{i}</div> - {(l.anchor1 as Doc).title} + {StrCast((l.anchor1 as Doc).title)} </div> ); } }); } - return <div className="capture-block"> - <div className="capture-block-title">Links</div> - <div className="capture-block-list"> - {order} + return ( + <div className="capture-block"> + <div className="capture-block-title">Links</div> + <div className="capture-block-list">{order}</div> </div> - </div>; + ); } @computed get closeButtons() { - return <div className="capture-block"> - <div className="buttons"> - <div className="save" onClick={() => { - LightboxView.SetLightboxDoc(this._document); - this.close(); - }}> - Save - </div> - <div className="cancel" onClick={() => { - const selected = SelectionManager.Views().slice(); - SelectionManager.DeselectAll(); - selected.map(dv => dv.props.removeDocument?.(dv.props.Document)); - this.close(); - }}> - Cancel + return ( + <div className="capture-block"> + <div className="buttons"> + <div + className="save" + onClick={() => { + LightboxView.SetLightboxDoc(this._document); + this.close(); + }}> + Save + </div> + <div + className="cancel" + onClick={() => { + const selected = SelectionManager.Views().slice(); + SelectionManager.DeselectAll(); + selected.map(dv => dv.props.removeDocument?.(dv.props.Document)); + this.close(); + }}> + Cancel + </div> </div> </div> - </div>; + ); } - - private get captureInterface() { - return <div className="capture-interface"> - <div className="capture-t1"> - <div className="recordButtonOutline" style={{}}> - <div className="recordButtonInner" style={{}}> + return ( + <div className="capture-interface"> + <div className="capture-t1"> + <div className="recordButtonOutline" style={{}}> + <div className="recordButtonInner" style={{}}></div> </div> + Conversation Capture </div> - Conversation Capture - </div> - <div className="capture-t2"> - - </div> - {this.visibilityContent} - {this.linksContent} - <div className="close-button" onClick={this.close}> - <FontAwesomeIcon icon={"times"} color="black" size={"lg"} /> + <div className="capture-t2"></div> + {this.visibilityContent} + {this.linksContent} + <div className="close-button" onClick={this.close}> + <FontAwesomeIcon icon={'times'} color="black" size={'lg'} /> + </div> + {this.closeButtons} </div> - {this.closeButtons} - </div>; - + ); } render() { - return <MainViewModal - contents={this.captureInterface} - isDisplayed={this.isOpen} - interactive={true} - closeOnExternalClick={this.close} - dialogueBoxStyle={{ width: "500px", height: "350px", border: "none", background: "whitesmoke" }} - overlayStyle={{ background: "black" }} - overlayDisplayedOpacity={0.6} - />; + return ( + <MainViewModal + contents={this.captureInterface} + isDisplayed={this.isOpen} + interactive={true} + closeOnExternalClick={this.close} + dialogueBoxStyle={{ width: '500px', height: '350px', border: 'none', background: 'whitesmoke' }} + overlayStyle={{ background: 'black' }} + overlayDisplayedOpacity={0.6} + /> + ); } -}
\ No newline at end of file +} diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index b2a5fddcd..84efcb966 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -957,7 +957,7 @@ export class CurrentUserUtils { } new LinkManager(); - DocServer.UPDATE_SERVER_CACHE(); + setTimeout(DocServer.UPDATE_SERVER_CACHE, 2500); return doc; } static setupFieldInfos(doc:Doc, field="fieldInfos") { diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx index 62d656c5d..59334f6a2 100644 --- a/src/client/util/GroupManager.tsx +++ b/src/client/util/GroupManager.tsx @@ -1,19 +1,19 @@ -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, observable } from "mobx"; -import { observer } from "mobx-react"; -import * as React from "react"; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, computed, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; import Select from 'react-select'; -import * as RequestPromise from "request-promise"; -import { Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc"; -import { StrCast, Cast } from "../../fields/Types"; -import { Utils } from "../../Utils"; -import { MainViewModal } from "../views/MainViewModal"; -import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox"; -import "./GroupManager.scss"; -import { GroupMemberView } from "./GroupMemberView"; -import { SharingManager, User } from "./SharingManager"; -import { listSpec } from "../../fields/Schema"; -import { DateField } from "../../fields/DateField"; +import * as RequestPromise from 'request-promise'; +import { Doc, DocListCast, DocListCastAsync, Opt } from '../../fields/Doc'; +import { StrCast, Cast } from '../../fields/Types'; +import { Utils } from '../../Utils'; +import { MainViewModal } from '../views/MainViewModal'; +import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox'; +import './GroupManager.scss'; +import { GroupMemberView } from './GroupMemberView'; +import { SharingManager, User } from './SharingManager'; +import { listSpec } from '../../fields/Schema'; +import { DateField } from '../../fields/DateField'; /** * Interface for options for the react-select component @@ -25,7 +25,6 @@ export interface UserOptions { @observer export class GroupManager extends React.Component<{}> { - static Instance: GroupManager; @observable isOpen: boolean = false; // whether the GroupManager is to be displayed or not. @observable private users: string[] = []; // list of users populated from the database. @@ -34,24 +33,26 @@ export class GroupManager extends React.Component<{}> { @observable private createGroupModalOpen: boolean = false; private inputRef: React.RefObject<HTMLInputElement> = React.createRef(); // the ref for the input box. private createGroupButtonRef: React.RefObject<HTMLButtonElement> = React.createRef(); // the ref for the group creation button - @observable private buttonColour: "#979797" | "black" = "#979797"; - @observable private groupSort: "ascending" | "descending" | "none" = "none"; + @observable private buttonColour: '#979797' | 'black' = '#979797'; + @observable private groupSort: 'ascending' | 'descending' | 'none' = 'none'; constructor(props: Readonly<{}>) { super(props); GroupManager.Instance = this; } - componentDidMount() { this.populateUsers(); } + componentDidMount() { + this.populateUsers(); + } /** * Fetches the list of users stored on the database. */ populateUsers = async () => { - const userList = await RequestPromise.get(Utils.prepend("/getUsers")); + const userList = await RequestPromise.get(Utils.prepend('/getUsers')); const raw = JSON.parse(userList) as User[]; raw.map(action(user => !this.users.some(umail => umail === user.email) && this.users.push(user.email))); - } + }; /** * @returns the options to be rendered in the dropdown menu to add users and create a group. @@ -68,11 +69,11 @@ export class GroupManager extends React.Component<{}> { // SelectionManager.DeselectAll(); this.isOpen = true; this.populateUsers(); - } + }; /** * Hides the GroupManager. - */ + */ @action close = () => { this.isOpen = false; @@ -81,26 +82,32 @@ export class GroupManager extends React.Component<{}> { // this.users = []; this.createGroupModalOpen = false; TaskCompletionBox.taskCompleted = false; - } + }; /** * @returns the database of groups. */ - @computed get GroupManagerDoc(): Doc | undefined { return Doc.UserDoc().globalGroupDatabase as Doc; } + @computed get GroupManagerDoc(): Doc | undefined { + return Doc.UserDoc().globalGroupDatabase as Doc; + } /** * @returns a list of all group documents. */ - @computed get allGroups(): Doc[] { return DocListCast(this.GroupManagerDoc?.data); } + @computed get allGroups(): Doc[] { + return DocListCast(this.GroupManagerDoc?.data); + } /** * @returns the members of the admin group. */ - @computed get adminGroupMembers(): string[] { return this.getGroup("Admin") ? JSON.parse(StrCast(this.getGroup("Admin")!.members)) : ""; } + @computed get adminGroupMembers(): string[] { + return this.getGroup('Admin') ? JSON.parse(StrCast(this.getGroup('Admin')!.members)) : ''; + } /** * @returns a group document based on the group name. - * @param groupName + * @param groupName */ getGroup(groupName: string): Doc | undefined { return this.allGroups.find(group => group.title === groupName); @@ -114,10 +121,9 @@ export class GroupManager extends React.Component<{}> { return JSON.parse(StrCast(this.getGroup(group)!.members)) as string[]; } - /** * @returns a boolean indicating whether the current user has access to edit group documents. - * @param groupDoc + * @param groupDoc */ hasEditAccess(groupDoc: Doc): boolean { if (!groupDoc) return false; @@ -127,12 +133,12 @@ export class GroupManager extends React.Component<{}> { /** * Helper method that sets up the group document. - * @param groupName - * @param memberEmails + * @param groupName + * @param memberEmails */ createGroupDoc(groupName: string, memberEmails: string[] = []) { - const name = groupName.toLowerCase() === "admin" ? "Admin" : groupName; - const groupDoc = new Doc("GROUP:" + name, true); + const name = groupName.toLowerCase() === 'admin' ? 'Admin' : groupName; + const groupDoc = new Doc('GROUP:' + name, true); groupDoc.title = name; groupDoc.owners = JSON.stringify([Doc.CurrentUserEmail]); groupDoc.members = JSON.stringify(memberEmails); @@ -141,12 +147,12 @@ export class GroupManager extends React.Component<{}> { /** * Helper method that adds a group document to the database of group documents and @returns whether it was successfully added or not. - * @param groupDoc + * @param groupDoc */ addGroup(groupDoc: Doc): boolean { if (this.GroupManagerDoc) { - Doc.AddDocToList(this.GroupManagerDoc, "data", groupDoc); - this.GroupManagerDoc["data-lastModified"] = new DateField; + Doc.AddDocToList(this.GroupManagerDoc, 'data', groupDoc); + this.GroupManagerDoc['data-lastModified'] = new DateField(); return true; } return false; @@ -154,20 +160,20 @@ export class GroupManager extends React.Component<{}> { /** * Deletes a group from the database of group documents and @returns whether the group was deleted or not. - * @param group + * @param group */ @action deleteGroup(group: Doc): boolean { if (group) { if (this.GroupManagerDoc && this.hasEditAccess(group)) { - Doc.RemoveDocFromList(this.GroupManagerDoc, "data", group); + Doc.RemoveDocFromList(this.GroupManagerDoc, 'data', group); SharingManager.Instance.removeGroup(group); const members = JSON.parse(StrCast(group.members)); if (members.includes(Doc.CurrentUserEmail)) { const index = DocListCast(this.GroupManagerDoc.data).findIndex(grp => grp === group); index !== -1 && Cast(this.GroupManagerDoc.data, listSpec(Doc), [])?.splice(index, 1); } - this.GroupManagerDoc["data-lastModified"] = new DateField; + this.GroupManagerDoc['data-lastModified'] = new DateField(); if (group === this.currentGroup) { this.currentGroup = undefined; } @@ -179,8 +185,8 @@ export class GroupManager extends React.Component<{}> { /** * Adds a member to a group. - * @param groupDoc - * @param email + * @param groupDoc + * @param email */ addMemberToGroup(groupDoc: Doc, email: string) { if (this.hasEditAccess(groupDoc)) { @@ -188,14 +194,14 @@ export class GroupManager extends React.Component<{}> { !memberList.includes(email) && memberList.push(email); groupDoc.members = JSON.stringify(memberList); SharingManager.Instance.shareWithAddedMember(groupDoc, email); - this.GroupManagerDoc && (this.GroupManagerDoc["data-lastModified"] = new DateField); + this.GroupManagerDoc && (this.GroupManagerDoc['data-lastModified'] = new DateField()); } } /** * Removes a member from the group. - * @param groupDoc - * @param email + * @param groupDoc + * @param email */ removeMemberFromGroup(groupDoc: Doc, email: string) { if (this.hasEditAccess(groupDoc)) { @@ -205,27 +211,27 @@ export class GroupManager extends React.Component<{}> { const user = memberList.splice(index, 1)[0]; groupDoc.members = JSON.stringify(memberList); SharingManager.Instance.removeMember(groupDoc, email); - this.GroupManagerDoc && (this.GroupManagerDoc["data-lastModified"] = new DateField); + this.GroupManagerDoc && (this.GroupManagerDoc['data-lastModified'] = new DateField()); } } } /** * Handles changes in the users selected in the "Select users" dropdown. - * @param selectedOptions + * @param selectedOptions */ @action handleChange = (selectedOptions: any) => { this.selectedUsers = selectedOptions as UserOptions[]; - } + }; /** * Creates the group when the enter key has been pressed (when in the input). - * @param e + * @param e */ handleKeyDown = (e: React.KeyboardEvent) => { - e.key === "Enter" && this.createGroup(); - } + e.key === 'Enter' && this.createGroup(); + }; /** * Handles the input of required fields in the setup of a group and resets the relevant variables. @@ -234,32 +240,37 @@ export class GroupManager extends React.Component<{}> { createGroup = () => { const { value } = this.inputRef.current!; if (!value) { - alert("Please enter a group name"); + alert('Please enter a group name'); return; } - if (["admin", "public", "override"].includes(value.toLowerCase())) { - if (value.toLowerCase() !== "admin" || (value.toLowerCase() === "admin" && this.getGroup("Admin"))) { + if (['admin', 'public', 'override'].includes(value.toLowerCase())) { + if (value.toLowerCase() !== 'admin' || (value.toLowerCase() === 'admin' && this.getGroup('Admin'))) { alert(`You cannot override the ${value.charAt(0).toUpperCase() + value.slice(1)} group`); return; } } if (this.getGroup(value)) { - alert("Please select a unique group name"); + alert('Please select a unique group name'); return; } - this.createGroupDoc(value, this.selectedUsers?.map(user => user.value)); + this.createGroupDoc( + value, + this.selectedUsers?.map(user => user.value) + ); this.selectedUsers = null; - this.inputRef.current!.value = ""; - this.buttonColour = "#979797"; + this.inputRef.current!.value = ''; + this.buttonColour = '#979797'; const { left, width, top } = this.createGroupButtonRef.current!.getBoundingClientRect(); TaskCompletionBox.popupX = left - 2 * width; TaskCompletionBox.popupY = top; - TaskCompletionBox.textDisplayed = "Group created!"; + TaskCompletionBox.textDisplayed = 'Group created!'; TaskCompletionBox.taskCompleted = true; - setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2000); - - } + setTimeout( + action(() => (TaskCompletionBox.taskCompleted = false)), + 2000 + ); + }; /** * @returns the MainViewModal which allows the user to create groups. @@ -268,50 +279,43 @@ export class GroupManager extends React.Component<{}> { const contents = ( <div className="group-create"> <div className="group-heading" style={{ marginBottom: 0 }}> - <p><b>New Group</b></p> - <div className={"close-button"} onClick={action(() => { - this.createGroupModalOpen = false; TaskCompletionBox.taskCompleted = false; - })}> - <FontAwesomeIcon icon={"times"} color={"black"} size={"lg"} /> + <p> + <b>New Group</b> + </p> + <div + className={'close-button'} + onClick={action(() => { + this.createGroupModalOpen = false; + TaskCompletionBox.taskCompleted = false; + })}> + <FontAwesomeIcon icon={'times'} color={'black'} size={'lg'} /> </div> </div> - <input - className="group-input" - ref={this.inputRef} - onKeyDown={this.handleKeyDown} - autoFocus - type="text" - placeholder="Group name" - onChange={action(() => this.buttonColour = this.inputRef.current?.value ? "black" : "#979797")} /> + <input className="group-input" ref={this.inputRef} onKeyDown={this.handleKeyDown} autoFocus type="text" placeholder="Group name" onChange={action(() => (this.buttonColour = this.inputRef.current?.value ? 'black' : '#979797'))} /> <Select isMulti options={this.options} onChange={this.handleChange} - placeholder={"Select users"} + placeholder={'Select users'} value={this.selectedUsers} closeMenuOnSelect={false} styles={{ dropdownIndicator: (base, state) => ({ ...base, transition: '0.5s all ease', - transform: state.selectProps.menuIsOpen ? 'rotate(180deg)' : undefined + transform: state.selectProps.menuIsOpen ? 'rotate(180deg)' : undefined, }), - multiValue: (base) => ({ + multiValue: base => ({ ...base, - maxWidth: "50%", + maxWidth: '50%', '&:hover': { - maxWidth: "unset" - } - }) + maxWidth: 'unset', + }, + }), }} /> - <button - ref={this.createGroupButtonRef} - onClick={this.createGroup} - style={{ background: this.buttonColour }} - disabled={this.buttonColour === "#979797"} - > + <button ref={this.createGroupButtonRef} onClick={this.createGroup} style={{ background: this.buttonColour }} disabled={this.buttonColour === '#979797'}> Create </button> </div> @@ -322,8 +326,12 @@ export class GroupManager extends React.Component<{}> { isDisplayed={this.createGroupModalOpen} interactive={true} contents={contents} - dialogueBoxStyle={{ width: "90%", height: "70%" }} - closeOnExternalClick={action(() => { this.createGroupModalOpen = false; this.selectedUsers = null; TaskCompletionBox.taskCompleted = false; })} + dialogueBoxStyle={{ width: '90%', height: '70%' }} + closeOnExternalClick={action(() => { + this.createGroupModalOpen = false; + this.selectedUsers = null; + TaskCompletionBox.taskCompleted = false; + })} /> ); } @@ -332,7 +340,6 @@ export class GroupManager extends React.Component<{}> { * A getter that @returns the main interface for the GroupManager. */ private get groupInterface() { - const sortGroups = (d1: Doc, d2: Doc) => { const g1 = StrCast(d1.title); const g2 = StrCast(d2.title); @@ -340,62 +347,50 @@ export class GroupManager extends React.Component<{}> { return g1 < g2 ? -1 : g1 === g2 ? 0 : 1; }; - const groups = this.groupSort === "ascending" ? this.allGroups.sort(sortGroups) : this.groupSort === "descending" ? this.allGroups.sort(sortGroups).reverse() : this.allGroups; + const groups = this.groupSort === 'ascending' ? this.allGroups.sort(sortGroups) : this.groupSort === 'descending' ? this.allGroups.sort(sortGroups).reverse() : this.allGroups; return ( <div className="group-interface"> {this.groupCreationModal} - {this.currentGroup ? - <GroupMemberView - group={this.currentGroup} - onCloseButtonClick={action(() => this.currentGroup = undefined)} - /> - : null} + {this.currentGroup ? <GroupMemberView group={this.currentGroup} onCloseButtonClick={action(() => (this.currentGroup = undefined))} /> : null} <div className="group-heading"> - <p><b>Manage Groups</b></p> - <button onClick={action(() => this.createGroupModalOpen = true)}> - <FontAwesomeIcon icon={"plus"} size={"sm"} /> Create Group + <p> + <b>Manage Groups</b> + </p> + <button onClick={action(() => (this.createGroupModalOpen = true))}> + <FontAwesomeIcon icon={'plus'} size={'sm'} /> Create Group </button> - <div className={"close-button"} onClick={this.close}> - <FontAwesomeIcon icon={"times"} color={"black"} size={"lg"} /> + <div className={'close-button'} onClick={this.close}> + <FontAwesomeIcon icon={'times'} color={'black'} size={'lg'} /> </div> </div> <div className="main-container"> - <div - className="sort-groups" - onClick={action(() => this.groupSort = this.groupSort === "ascending" ? "descending" : this.groupSort === "descending" ? "none" : "ascending")}> - Name {this.groupSort === "ascending" ? <FontAwesomeIcon icon={"caret-up"} size={"xs"} /> - : this.groupSort === "descending" ? <FontAwesomeIcon icon={"caret-down"} size={"xs"} /> - : <FontAwesomeIcon icon={"caret-right"} size={"xs"} /> - } + <div className="sort-groups" onClick={action(() => (this.groupSort = this.groupSort === 'ascending' ? 'descending' : this.groupSort === 'descending' ? 'none' : 'ascending'))}> + Name{' '} + {this.groupSort === 'ascending' ? ( + <FontAwesomeIcon icon={'caret-up'} size={'xs'} /> + ) : this.groupSort === 'descending' ? ( + <FontAwesomeIcon icon={'caret-down'} size={'xs'} /> + ) : ( + <FontAwesomeIcon icon={'caret-right'} size={'xs'} /> + )} </div> <div className="group-body"> - {groups.map(group => - <div - className="group-row" - key={StrCast(group.title || group.groupName)} - > - <div className="group-name" >{group.title || group.groupName}</div> - <div className="group-info" onClick={action(() => this.currentGroup = group)}> - <FontAwesomeIcon icon={"info-circle"} color={"#e8e8e8"} size={"sm"} style={{ backgroundColor: "#1e89d7", borderRadius: "100%", border: "1px solid #1e89d7" }} /> + {groups.map(group => ( + <div className="group-row" key={StrCast(group.title || group.groupName)}> + <div className="group-name">{StrCast(group.title || group.groupName)}</div> + <div className="group-info" onClick={action(() => (this.currentGroup = group))}> + <FontAwesomeIcon icon={'info-circle'} color={'#e8e8e8'} size={'sm'} style={{ backgroundColor: '#1e89d7', borderRadius: '100%', border: '1px solid #1e89d7' }} /> </div> </div> - )} + ))} </div> </div> - </div> ); } render() { - return <MainViewModal - contents={this.groupInterface} - isDisplayed={this.isOpen} - interactive={true} - dialogueBoxStyle={{ zIndex: 1002 }} - overlayStyle={{ zIndex: 1001 }} - closeOnExternalClick={this.close} - />; + return <MainViewModal contents={this.groupInterface} isDisplayed={this.isOpen} interactive={true} dialogueBoxStyle={{ zIndex: 1002 }} overlayStyle={{ zIndex: 1001 }} closeOnExternalClick={this.close} />; } -}
\ No newline at end of file +} diff --git a/src/client/util/ScrollBox.tsx b/src/client/util/ScrollBox.tsx index a209874a3..d4620ae3f 100644 --- a/src/client/util/ScrollBox.tsx +++ b/src/client/util/ScrollBox.tsx @@ -1,21 +1,24 @@ -import React = require("react"); +import React = require('react'); -export class ScrollBox extends React.Component { +export class ScrollBox extends React.Component<React.PropsWithChildren<{}>> { onWheel = (e: React.WheelEvent) => { - if (e.currentTarget.scrollHeight > e.currentTarget.clientHeight) { // If the element has a scroll bar, then we don't want the containing collection to zoom + if (e.currentTarget.scrollHeight > e.currentTarget.clientHeight) { + // If the element has a scroll bar, then we don't want the containing collection to zoom e.stopPropagation(); } - } + }; render() { return ( - <div style={{ - overflow: "auto", - width: "100%", - height: "100%", - }} onWheel={this.onWheel}> + <div + style={{ + overflow: 'auto', + width: '100%', + height: '100%', + }} + onWheel={this.onWheel}> {this.props.children} </div> ); } -}
\ No newline at end of file +} diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index d77633b8d..1acb3ab38 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -1,29 +1,29 @@ -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { intersection } from "lodash"; -import { action, computed, observable, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import * as React from "react"; -import Select from "react-select"; -import * as RequestPromise from "request-promise"; -import { AclAugment, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, AclUnset, DataSym, Doc, DocListCast, DocListCastAsync, Opt, AclSelfEdit } from "../../fields/Doc"; -import { List } from "../../fields/List"; -import { Cast, NumCast, StrCast } from "../../fields/Types"; -import { distributeAcls, GetEffectiveAcl, normalizeEmail, SharingPermissions, TraceMobx } from "../../fields/util"; -import { Utils } from "../../Utils"; -import { DocServer } from "../DocServer"; -import { CollectionView } from "../views/collections/CollectionView"; -import { DictationOverlay } from "../views/DictationOverlay"; -import { MainViewModal } from "../views/MainViewModal"; -import { DocumentView } from "../views/nodes/DocumentView"; -import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox"; -import { SearchBox } from "../views/search/SearchBox"; -import { CurrentUserUtils } from "./CurrentUserUtils"; -import { DocumentManager } from "./DocumentManager"; -import { GroupManager, UserOptions } from "./GroupManager"; -import { GroupMemberView } from "./GroupMemberView"; -import { SelectionManager } from "./SelectionManager"; -import "./SharingManager.scss"; -import { LinkManager } from "./LinkManager"; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { intersection } from 'lodash'; +import { action, computed, observable, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import Select from 'react-select'; +import * as RequestPromise from 'request-promise'; +import { AclAugment, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, AclUnset, DataSym, Doc, DocListCast, DocListCastAsync, Opt, AclSelfEdit } from '../../fields/Doc'; +import { List } from '../../fields/List'; +import { Cast, NumCast, StrCast } from '../../fields/Types'; +import { distributeAcls, GetEffectiveAcl, normalizeEmail, SharingPermissions, TraceMobx } from '../../fields/util'; +import { Utils } from '../../Utils'; +import { DocServer } from '../DocServer'; +import { CollectionView } from '../views/collections/CollectionView'; +import { DictationOverlay } from '../views/DictationOverlay'; +import { MainViewModal } from '../views/MainViewModal'; +import { DocumentView } from '../views/nodes/DocumentView'; +import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox'; +import { SearchBox } from '../views/search/SearchBox'; +import { CurrentUserUtils } from './CurrentUserUtils'; +import { DocumentManager } from './DocumentManager'; +import { GroupManager, UserOptions } from './GroupManager'; +import { GroupMemberView } from './GroupMemberView'; +import { SelectionManager } from './SelectionManager'; +import './SharingManager.scss'; +import { LinkManager } from './LinkManager'; export interface User { email: string; @@ -44,19 +44,19 @@ interface GroupedOptions { // const DefaultColor = "black"; // used to differentiate between individuals and groups when sharing -const indType = "!indType/"; -const groupType = "!groupType/"; +const indType = '!indType/'; +const groupType = '!groupType/'; -const storage = "data"; +const storage = 'data'; /** * A user who also has a sharing doc. */ interface ValidatedUser { - user: User; // database minimal info to identify / communicate with a user (email, sharing doc id) - sharingDoc: Doc; // document to share/message another user + user: User; // database minimal info to identify / communicate with a user (email, sharing doc id) + sharingDoc: Doc; // document to share/message another user linkDatabase: Doc; - userColor: string; // stored on the sharinDoc, extracted for convenience? + userColor: string; // stored on the sharinDoc, extracted for convenience? } @observer @@ -71,8 +71,8 @@ export class SharingManager extends React.Component<{}> { @observable private overlayOpacity = 0.4; // for the modal @observable private selectedUsers: UserOptions[] | null = null; // users (individuals/groups) selected to share with @observable private permissions: SharingPermissions = SharingPermissions.Edit; // the permission with which to share with other users - @observable private individualSort: "ascending" | "descending" | "none" = "none"; // sorting options for the list of individuals - @observable private groupSort: "ascending" | "descending" | "none" = "none"; // sorting options for the list of groups + @observable private individualSort: 'ascending' | 'descending' | 'none' = 'none'; // sorting options for the list of individuals + @observable private groupSort: 'ascending' | 'descending' | 'none' = 'none'; // sorting options for the list of groups private shareDocumentButtonRef: React.RefObject<HTMLButtonElement> = React.createRef(); // ref for the share button, used for the position of the popup private distributeAclsButtonRef: React.RefObject<HTMLButtonElement> = React.createRef(); // ref for the distribute button, used for the position of the popup // if both showUserOptions and showGroupOptions are false then both are displayed @@ -89,7 +89,7 @@ export class SharingManager extends React.Component<{}> { [AclAugment, SharingPermissions.Augment], [AclSelfEdit, SharingPermissions.SelfEdit], [AclEdit, SharingPermissions.Edit], - [AclAdmin, SharingPermissions.Admin] + [AclAdmin, SharingPermissions.Admin], ]); // private get linkVisible() { @@ -105,17 +105,20 @@ export class SharingManager extends React.Component<{}> { this.isOpen = this.targetDoc !== undefined; this.permissions = SharingPermissions.Augment; }); - } + }; public close = action(() => { this.isOpen = false; this.selectedUsers = null; // resets the list of users and selected users (in the react-select component) TaskCompletionBox.taskCompleted = false; - setTimeout(action(() => { - // this.copied = false; - DictationOverlay.Instance.hasActiveModal = false; - this.targetDoc = undefined; - }), 500); + setTimeout( + action(() => { + // this.copied = false; + DictationOverlay.Instance.hasActiveModal = false; + this.targetDoc = undefined; + }), + 500 + ); }); constructor(props: {}) { @@ -136,7 +139,7 @@ export class SharingManager extends React.Component<{}> { populateUsers = async () => { if (!this.populating) { this.populating = true; - const userList = await RequestPromise.get(Utils.prepend("/getUsers")); + const userList = await RequestPromise.get(Utils.prepend('/getUsers')); const raw = JSON.parse(userList) as User[]; const sharingDocs: ValidatedUser[] = []; const evaluating = raw.map(async user => { @@ -146,7 +149,8 @@ export class SharingManager extends React.Component<{}> { const linkDatabase = await DocServer.GetRefField(user.linkDatabaseId); if (sharingDoc instanceof Doc && linkDatabase instanceof Doc) { await DocListCastAsync(linkDatabase.data); - (await DocListCastAsync(Cast(linkDatabase, Doc, null).data))?.forEach(async link => { // makes sure link anchors are loaded to avoid incremental updates to computedFns in LinkManager + (await DocListCastAsync(Cast(linkDatabase, Doc, null).data))?.forEach(async link => { + // makes sure link anchors are loaded to avoid incremental updates to computedFns in LinkManager const a1 = await Cast(link?.anchor1, Doc, null); const a2 = await Cast(link?.anchor2, Doc, null); }); @@ -166,7 +170,7 @@ export class SharingManager extends React.Component<{}> { this.populating = false; }); } - } + }; /** * Shares the document with a user. @@ -179,31 +183,31 @@ export class SharingManager extends React.Component<{}> { const isDashboard = DocListCast(CurrentUserUtils.MyDashboards.data).indexOf(target) !== -1; const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.props.Document); - return !docs.map(doc => { - doc.author === Doc.CurrentUserEmail && !doc[myAcl] && distributeAcls(myAcl, SharingPermissions.Admin, doc, undefined, undefined, isDashboard); - - if (permission === SharingPermissions.None) { - if (doc[acl] && doc[acl] !== SharingPermissions.None) doc.numUsersShared = NumCast(doc.numUsersShared, 1) - 1; - } - else { - if (!doc[acl] || doc[acl] === SharingPermissions.None) doc.numUsersShared = NumCast(doc.numUsersShared, 0) + 1; - } + return !docs + .map(doc => { + doc.author === Doc.CurrentUserEmail && !doc[myAcl] && distributeAcls(myAcl, SharingPermissions.Admin, doc, undefined, undefined, isDashboard); + + if (permission === SharingPermissions.None) { + if (doc[acl] && doc[acl] !== SharingPermissions.None) doc.numUsersShared = NumCast(doc.numUsersShared, 1) - 1; + } else { + if (!doc[acl] || doc[acl] === SharingPermissions.None) doc.numUsersShared = NumCast(doc.numUsersShared, 0) + 1; + } - distributeAcls(acl, permission as SharingPermissions, doc, undefined, undefined, isDashboard); + distributeAcls(acl, permission as SharingPermissions, doc, undefined, undefined, isDashboard); - this.setDashboardBackground(doc, permission as SharingPermissions); - if (permission !== SharingPermissions.None) return Doc.AddDocToList(sharingDoc, storage, doc); - else return GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc || doc)); - }).some(success => !success); - } + this.setDashboardBackground(doc, permission as SharingPermissions); + if (permission !== SharingPermissions.None) return Doc.AddDocToList(sharingDoc, storage, doc); + else return GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc) || doc); + }) + .some(success => !success); + }; /** * Sets the permission on the target for the group. - * @param group - * @param permission + * @param group + * @param permission */ setInternalGroupSharing = (group: Doc | { title: string }, permission: string, targetDoc?: Doc) => { - const target = targetDoc || this.targetDoc!; const key = normalizeEmail(StrCast(group.title)); const acl = `acl-${key}`; @@ -212,38 +216,41 @@ export class SharingManager extends React.Component<{}> { const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.props.Document); // ! ensures it returns true if document has been shared successfully, false otherwise - return !docs.map(doc => { - doc.author === Doc.CurrentUserEmail && !doc[`acl-${Doc.CurrentUserEmailNormalized}`] && distributeAcls(`acl-${Doc.CurrentUserEmailNormalized}`, SharingPermissions.Admin, doc, undefined, undefined, isDashboard); - - if (permission === SharingPermissions.None) { - if (doc[acl] && doc[acl] !== SharingPermissions.None) doc.numGroupsShared = NumCast(doc.numGroupsShared, 1) - 1; - } - else { - if (!doc[acl] || doc[acl] === SharingPermissions.None) doc.numGroupsShared = NumCast(doc.numGroupsShared, 0) + 1; - } + return !docs + .map(doc => { + doc.author === Doc.CurrentUserEmail && !doc[`acl-${Doc.CurrentUserEmailNormalized}`] && distributeAcls(`acl-${Doc.CurrentUserEmailNormalized}`, SharingPermissions.Admin, doc, undefined, undefined, isDashboard); + + if (permission === SharingPermissions.None) { + if (doc[acl] && doc[acl] !== SharingPermissions.None) doc.numGroupsShared = NumCast(doc.numGroupsShared, 1) - 1; + } else { + if (!doc[acl] || doc[acl] === SharingPermissions.None) doc.numGroupsShared = NumCast(doc.numGroupsShared, 0) + 1; + } - distributeAcls(acl, permission as SharingPermissions, doc, undefined, undefined, isDashboard); - this.setDashboardBackground(doc, permission as SharingPermissions); + distributeAcls(acl, permission as SharingPermissions, doc, undefined, undefined, isDashboard); + this.setDashboardBackground(doc, permission as SharingPermissions); - if (group instanceof Doc) { - const members: string[] = JSON.parse(StrCast(group.members)); - const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email)); + if (group instanceof Doc) { + const members: string[] = JSON.parse(StrCast(group.members)); + const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email)); - // if documents have been shared, add the doc to that list if it doesn't already exist, otherwise create a new list with the doc - group.docsShared ? Doc.IndexOf(doc, DocListCast(group.docsShared)) === -1 && (group.docsShared as List<Doc>).push(doc) : group.docsShared = new List<Doc>([doc]); + // if documents have been shared, add the doc to that list if it doesn't already exist, otherwise create a new list with the doc + group.docsShared ? Doc.IndexOf(doc, DocListCast(group.docsShared)) === -1 && (group.docsShared as List<Doc>).push(doc) : (group.docsShared = new List<Doc>([doc])); - return users.map(({ user, sharingDoc }) => { - if (permission !== SharingPermissions.None) return Doc.AddDocToList(sharingDoc, storage, doc); // add the doc to the sharingDoc if it hasn't already been added - else return GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc || doc)); // remove the doc from the list if it already exists - }).some(success => !success); - } - }).some(success => success); - } + return users + .map(({ user, sharingDoc }) => { + if (permission !== SharingPermissions.None) return Doc.AddDocToList(sharingDoc, storage, doc); // add the doc to the sharingDoc if it hasn't already been added + else return GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc) || doc); // remove the doc from the list if it already exists + }) + .some(success => !success); + } + }) + .some(success => success); + }; /** * Shares the documents shared with a group with a new user who has been added to that group. - * @param group - * @param emailId + * @param group + * @param emailId */ shareWithAddedMember = (group: Doc, emailId: string, retry: boolean = true) => { const user = this.users.find(({ user: { email } }) => email === emailId)!; @@ -255,30 +262,30 @@ export class SharingManager extends React.Component<{}> { DocListCastAsync(group.docsShared).then(dl => { const filtered = dl?.filter(doc => !userdocs?.includes(doc)); filtered && userdocs?.push(...filtered); - })); + }) + ); } } - } + }; /** * Called from the properties sidebar to change permissions of a user. */ shareFromPropertiesSidebar = (shareWith: string, permission: SharingPermissions, docs: Doc[]) => { - if (shareWith !== "Public" && shareWith !== "Override") { - const user = this.users.find(({ user: { email } }) => email === (shareWith === "Me" ? Doc.CurrentUserEmail : shareWith)); + if (shareWith !== 'Public' && shareWith !== 'Override') { + const user = this.users.find(({ user: { email } }) => email === (shareWith === 'Me' ? Doc.CurrentUserEmail : shareWith)); docs.forEach(doc => { if (user) this.setInternalSharing(user, permission, doc); else this.setInternalGroupSharing(GroupManager.Instance.getGroup(shareWith)!, permission, doc); }); - } - else { + } else { const dashboards = DocListCast(CurrentUserUtils.MyDashboards.data); docs.forEach(doc => { const isDashboard = dashboards.indexOf(doc) !== -1; if (GetEffectiveAcl(doc) === AclAdmin) distributeAcls(`acl-${shareWith}`, permission, doc, undefined, undefined, isDashboard); }); } - } + }; /** * Sets the background of the Dashboard if it has been shared as a visual indicator @@ -287,22 +294,21 @@ export class SharingManager extends React.Component<{}> { if (Doc.IndexOf(doc, DocListCast(CurrentUserUtils.MyDashboards.data)) !== -1) { if (permission !== SharingPermissions.None) { doc.isShared = true; - doc.backgroundColor = "green"; - } - else { + doc.backgroundColor = 'green'; + } else { const acls = doc[DataSym][AclSym]; - if (Object.keys(acls).every(key => key === `acl-${Doc.CurrentUserEmailNormalized}` ? true : [AclUnset, AclPrivate].includes(acls[key]))) { + if (Object.keys(acls).every(key => (key === `acl-${Doc.CurrentUserEmailNormalized}` ? true : [AclUnset, AclPrivate].includes(acls[key])))) { doc.isShared = undefined; doc.backgroundColor = undefined; } } } - } + }; /** * Removes the documents shared with a user through a group when the user is removed from the group. - * @param group - * @param emailId + * @param group + * @param emailId */ removeMember = (group: Doc, emailId: string) => { const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!; @@ -315,11 +321,11 @@ export class SharingManager extends React.Component<{}> { }) ); } - } + }; /** * Removes a group's permissions from documents that have been shared with it. - * @param group + * @param group */ removeGroup = (group: Doc) => { if (group.docsShared) { @@ -335,9 +341,7 @@ export class SharingManager extends React.Component<{}> { users.forEach(({ sharingDoc }) => Doc.RemoveDocFromList(sharingDoc, storage, doc)); }); } - } - - + }; // private setExternalSharing = (permission: string) => { // const targetDoc = this.targetDoc; @@ -367,23 +371,23 @@ export class SharingManager extends React.Component<{}> { */ private sharingOptions(uniform: boolean, override?: boolean) { const dropdownValues: string[] = Object.values(SharingPermissions); - if (!uniform) dropdownValues.unshift("-multiple-"); - if (override) dropdownValues.unshift("None"); - return dropdownValues.filter(permission => !Doc.noviceMode || ![SharingPermissions.View, SharingPermissions.SelfEdit].includes(permission as any)).map(permission => - ( - <option key={permission} value={permission}> - {permission} - </option> - ) - ); + if (!uniform) dropdownValues.unshift('-multiple-'); + if (override) dropdownValues.unshift('None'); + return dropdownValues + .filter(permission => !Doc.noviceMode || ![SharingPermissions.View, SharingPermissions.SelfEdit].includes(permission as any)) + .map(permission => ( + <option key={permission} value={permission}> + {permission} + </option> + )); } private focusOn = (contents: string) => { - const title = this.targetDoc ? StrCast(this.targetDoc.title) : ""; + const title = this.targetDoc ? StrCast(this.targetDoc.title) : ''; const docs = SelectionManager.Views().length > 1 ? SelectionManager.Views().map(docView => docView.props.Document) : [this.targetDoc]; return ( <span - className={"focus-span"} + className={'focus-span'} title={title} onClick={() => { let context: Opt<CollectionView>; @@ -404,12 +408,11 @@ export class SharingManager extends React.Component<{}> { this.dialogueBoxOpacity = 1; this.overlayOpacity = 0.4; } - })} - > + })}> {contents} </span> ); - } + }; /** * Handles changes in the users selected in react-select @@ -417,7 +420,7 @@ export class SharingManager extends React.Component<{}> { @action handleUsersChange = (selectedOptions: any) => { this.selectedUsers = selectedOptions as UserOptions[]; - } + }; /** * Handles changes in the permission chosen to share with someone with @@ -425,7 +428,7 @@ export class SharingManager extends React.Component<{}> { @action handlePermissionsChange = (event: React.ChangeEvent<HTMLSelectElement>) => { this.permissions = event.currentTarget.value as SharingPermissions; - } + }; /** * Calls the relevant method for sharing, displays the popup, and resets the relevant variables. @@ -436,8 +439,7 @@ export class SharingManager extends React.Component<{}> { this.selectedUsers.forEach(user => { if (user.value.includes(indType)) { this.setInternalSharing(this.users.find(u => u.user.email === user.label)!, this.permissions); - } - else { + } else { this.setInternalGroupSharing(GroupManager.Instance.getGroup(user.label)!, this.permissions); } }); @@ -445,13 +447,16 @@ export class SharingManager extends React.Component<{}> { const { left, width, top, height } = this.shareDocumentButtonRef.current!.getBoundingClientRect(); TaskCompletionBox.popupX = left - 1.5 * width; TaskCompletionBox.popupY = top - 1.5 * height; - TaskCompletionBox.textDisplayed = "Document shared!"; + TaskCompletionBox.textDisplayed = 'Document shared!'; TaskCompletionBox.taskCompleted = true; - setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2000); + setTimeout( + action(() => (TaskCompletionBox.taskCompleted = false)), + 2000 + ); this.selectedUsers = null; } - } + }; // distributeOverCollection = (targetDoc?: Doc) => { // const target = targetDoc || this.targetDoc!; @@ -471,7 +476,7 @@ export class SharingManager extends React.Component<{}> { const { email: e1 } = u1.user; const { email: e2 } = u2.user; return e1 < e2 ? -1 : e1 === e2 ? 0 : 1; - } + }; /** * Sorting algorithm to sort groups. @@ -480,7 +485,7 @@ export class SharingManager extends React.Component<{}> { const g1 = StrCast(group1.title); const g2 = StrCast(group2.title); return g1 < g2 ? -1 : g1 === g2 ? 0 : 1; - } + }; /** * @returns the main interface of the SharingManager. @@ -488,28 +493,29 @@ export class SharingManager extends React.Component<{}> { @computed get sharingInterface() { TraceMobx(); const groupList = GroupManager.Instance?.allGroups || []; - const sortedUsers = this.users.slice().sort(this.sortUsers).map(({ user: { email } }) => ({ label: email, value: indType + email })); - const sortedGroups = groupList.slice().sort(this.sortGroups).map(({ title }) => ({ label: StrCast(title), value: groupType + StrCast(title) })); + const sortedUsers = this.users + .slice() + .sort(this.sortUsers) + .map(({ user: { email } }) => ({ label: email, value: indType + email })); + const sortedGroups = groupList + .slice() + .sort(this.sortGroups) + .map(({ title }) => ({ label: StrCast(title), value: groupType + StrCast(title) })); // the next block handles the users shown (individuals/groups/both) const options: GroupedOptions[] = []; if (GroupManager.Instance) { if ((this.showUserOptions && this.showGroupOptions) || (!this.showUserOptions && !this.showGroupOptions)) { - options.push( - { label: 'Individuals', options: sortedUsers }, - { label: 'Groups', options: sortedGroups }); - } - else if (this.showUserOptions) options.push({ label: 'Individuals', options: sortedUsers }); + options.push({ label: 'Individuals', options: sortedUsers }, { label: 'Groups', options: sortedGroups }); + } else if (this.showUserOptions) options.push({ label: 'Individuals', options: sortedUsers }); else options.push({ label: 'Groups', options: sortedGroups }); } - const users = this.individualSort === "ascending" ? this.users.slice().sort(this.sortUsers) : this.individualSort === "descending" ? this.users.slice().sort(this.sortUsers).reverse() : this.users; - const groups = this.groupSort === "ascending" ? groupList.slice().sort(this.sortGroups) : this.groupSort === "descending" ? groupList.slice().sort(this.sortGroups).reverse() : groupList; + const users = this.individualSort === 'ascending' ? this.users.slice().sort(this.sortUsers) : this.individualSort === 'descending' ? this.users.slice().sort(this.sortUsers).reverse() : this.users; + const groups = this.groupSort === 'ascending' ? groupList.slice().sort(this.sortGroups) : this.groupSort === 'descending' ? groupList.slice().sort(this.sortGroups).reverse() : groupList; // handles the case where multiple documents are selected - let docs = SelectionManager.Views().length < 2 ? - [this.layoutDocAcls ? this.targetDoc : this.targetDoc?.[DataSym]] - : SelectionManager.Views().map(docView => this.layoutDocAcls ? docView.props.Document : docView.props.Document?.[DataSym]); + let docs = SelectionManager.Views().length < 2 ? [this.layoutDocAcls ? this.targetDoc : this.targetDoc?.[DataSym]] : SelectionManager.Views().map(docView => (this.layoutDocAcls ? docView.props.Document : docView.props.Document?.[DataSym])); if (this.myDocAcls) { const newDocs: Doc[] = []; @@ -524,107 +530,78 @@ export class SharingManager extends React.Component<{}> { const admin = this.myDocAcls ? Boolean(docs.length) : effectiveAcls.every(acl => acl === AclAdmin); // users in common between all docs - const commonKeys = intersection(...docs.map(doc => this.layoutDocAcls ? doc?.[AclSym] && Object.keys(doc[AclSym]) : doc?.[DataSym]?.[AclSym] && Object.keys(doc[DataSym][AclSym]))); + const commonKeys = intersection(...docs.map(doc => (this.layoutDocAcls ? doc?.[AclSym] && Object.keys(doc[AclSym]) : doc?.[DataSym]?.[AclSym] && Object.keys(doc[DataSym][AclSym])))); // the list of users shared with - const userListContents: (JSX.Element | null)[] = users.filter(({ user }) => docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(user.email)}`) : docs[0]?.author !== user.email).map(({ user, linkDatabase, sharingDoc, userColor }) => { - const userKey = `acl-${normalizeEmail(user.email)}`; - const uniform = docs.every(doc => this.layoutDocAcls ? doc?.[AclSym]?.[userKey] === docs[0]?.[AclSym]?.[userKey] : doc?.[DataSym]?.[AclSym]?.[userKey] === docs[0]?.[DataSym]?.[AclSym]?.[userKey]); - const permissions = uniform ? StrCast(targetDoc?.[userKey]) : "-multiple-"; - - return !permissions ? (null) : ( - <div - key={userKey} - className={"container"} - > - <span className={"padding"}>{user.email}</span> - <div className="edit-actions"> - {admin || this.myDocAcls ? ( - <select - className={"permissions-dropdown"} - value={permissions} - onChange={e => this.setInternalSharing({ user, linkDatabase, sharingDoc, userColor }, e.currentTarget.value)} - > - {this.sharingOptions(uniform)} - </select> - ) : ( - <div className={"permissions-dropdown"}> - {permissions} - </div> - )} + const userListContents: (JSX.Element | null)[] = users + .filter(({ user }) => (docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(user.email)}`) : docs[0]?.author !== user.email)) + .map(({ user, linkDatabase, sharingDoc, userColor }) => { + const userKey = `acl-${normalizeEmail(user.email)}`; + const uniform = docs.every(doc => (this.layoutDocAcls ? doc?.[AclSym]?.[userKey] === docs[0]?.[AclSym]?.[userKey] : doc?.[DataSym]?.[AclSym]?.[userKey] === docs[0]?.[DataSym]?.[AclSym]?.[userKey])); + const permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-'; + + return !permissions ? null : ( + <div key={userKey} className={'container'}> + <span className={'padding'}>{user.email}</span> + <div className="edit-actions"> + {admin || this.myDocAcls ? ( + <select className={'permissions-dropdown'} value={permissions} onChange={e => this.setInternalSharing({ user, linkDatabase, sharingDoc, userColor }, e.currentTarget.value)}> + {this.sharingOptions(uniform)} + </select> + ) : ( + <div className={'permissions-dropdown'}>{permissions}</div> + )} + </div> </div> - </div> - ); - }); + ); + }); // checks if every doc has the same author const sameAuthor = docs.every(doc => doc?.author === docs[0]?.author); // the owner of the doc and the current user are placed at the top of the user list. userListContents.unshift( - sameAuthor ? - ( - <div - key={"owner"} - className={"container"} - > - <span className={"padding"}>{targetDoc?.author === Doc.CurrentUserEmail ? "Me" : targetDoc?.author}</span> - <div className="edit-actions"> - <div className={"permissions-dropdown"}> - Owner - </div> - </div> + sameAuthor ? ( + <div key={'owner'} className={'container'}> + <span className={'padding'}>{targetDoc?.author === Doc.CurrentUserEmail ? 'Me' : targetDoc?.author}</span> + <div className="edit-actions"> + <div className={'permissions-dropdown'}>Owner</div> </div> - ) : null, - sameAuthor && targetDoc?.author !== Doc.CurrentUserEmail ? - ( - <div - key={"me"} - className={"container"} - > - <span className={"padding"}>Me</span> - <div className="edit-actions"> - <div className={"permissions-dropdown"}> - {effectiveAcls.every(acl => acl === effectiveAcls[0]) ? this.AclMap.get(effectiveAcls[0])! : "-multiple-"} - </div> - </div> + </div> + ) : null, + sameAuthor && targetDoc?.author !== Doc.CurrentUserEmail ? ( + <div key={'me'} className={'container'}> + <span className={'padding'}>Me</span> + <div className="edit-actions"> + <div className={'permissions-dropdown'}>{effectiveAcls.every(acl => acl === effectiveAcls[0]) ? this.AclMap.get(effectiveAcls[0])! : '-multiple-'}</div> </div> - ) : null + </div> + ) : null ); - // the list of groups shared with - const groupListMap: (Doc | { title: string })[] = groups.filter(({ title }) => docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(StrCast(title))}`) : true); - groupListMap.unshift({ title: "Public" });//, { title: "ALL" }); + const groupListMap: (Doc | { title: string })[] = groups.filter(({ title }) => (docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(StrCast(title))}`) : true)); + groupListMap.unshift({ title: 'Public' }); //, { title: "ALL" }); const groupListContents = groupListMap.map(group => { const groupKey = `acl-${StrCast(group.title)}`; - const uniform = docs.every(doc => this.layoutDocAcls ? doc?.[AclSym]?.[groupKey] === docs[0]?.[AclSym]?.[groupKey] : doc?.[DataSym]?.[AclSym]?.[groupKey] === docs[0]?.[DataSym]?.[AclSym]?.[groupKey]); - const permissions = uniform ? StrCast(targetDoc?.[`acl-${StrCast(group.title)}`]) : "-multiple-"; - - return !permissions ? (null) : ( - <div - key={groupKey} - className={"container"} - > - <div className={"padding"}>{group.title}</div> - {group instanceof Doc ? - (<div className="group-info" onClick={action(() => GroupManager.Instance.currentGroup = group)}> - <FontAwesomeIcon icon={"info-circle"} color={"#e8e8e8"} size={"sm"} style={{ backgroundColor: "#1e89d7", borderRadius: "100%", border: "1px solid #1e89d7" }} /> - </div>) - : (null)} + const uniform = docs.every(doc => (this.layoutDocAcls ? doc?.[AclSym]?.[groupKey] === docs[0]?.[AclSym]?.[groupKey] : doc?.[DataSym]?.[AclSym]?.[groupKey] === docs[0]?.[DataSym]?.[AclSym]?.[groupKey])); + const permissions = uniform ? StrCast(targetDoc?.[`acl-${StrCast(group.title)}`]) : '-multiple-'; + + return !permissions ? null : ( + <div key={groupKey} className={'container'}> + <div className={'padding'}>{StrCast(group.title)}</div> + {group instanceof Doc ? ( + <div className="group-info" onClick={action(() => (GroupManager.Instance.currentGroup = group))}> + <FontAwesomeIcon icon={'info-circle'} color={'#e8e8e8'} size={'sm'} style={{ backgroundColor: '#1e89d7', borderRadius: '100%', border: '1px solid #1e89d7' }} /> + </div> + ) : null} <div className="edit-actions"> {admin || this.myDocAcls ? ( - <select - className={"permissions-dropdown"} - value={permissions} - onChange={e => this.setInternalGroupSharing(group, e.currentTarget.value)} - > - {this.sharingOptions(uniform, group.title === "Override")} + <select className={'permissions-dropdown'} value={permissions} onChange={e => this.setInternalGroupSharing(group, e.currentTarget.value)}> + {this.sharingOptions(uniform, group.title === 'Override')} </select> ) : ( - <div className={"permissions-dropdown"}> - {permissions} - </div> + <div className={'permissions-dropdown'}>{permissions}</div> )} </div> </div> @@ -632,102 +609,95 @@ export class SharingManager extends React.Component<{}> { }); return ( - <div className={"sharing-interface"}> - {GroupManager.Instance?.currentGroup ? - <GroupMemberView - group={GroupManager.Instance.currentGroup} - onCloseButtonClick={action(() => GroupManager.Instance.currentGroup = undefined)} - /> : - null} + <div className={'sharing-interface'}> + {GroupManager.Instance?.currentGroup ? <GroupMemberView group={GroupManager.Instance.currentGroup} onCloseButtonClick={action(() => (GroupManager.Instance.currentGroup = undefined))} /> : null} <div className="sharing-contents"> - <p className={"share-title"}><b>Share </b>{this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, "this document") : "-multiple-")}</p> - <div className={"close-button"} onClick={this.close}> - <FontAwesomeIcon icon={"times"} color={"black"} size={"lg"} /> + <p className={'share-title'}> + <b>Share </b> + {this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')} + </p> + <div className={'close-button'} onClick={this.close}> + <FontAwesomeIcon icon={'times'} color={'black'} size={'lg'} /> </div> {/* {this.linkVisible ? <div> {this.sharingUrl} </div> : (null)} */} - {<div className="share-container"> - <div className="share-setup"> - <Select - className="user-search" - placeholder="Enter user or group name..." - isMulti - isSearchable - closeMenuOnSelect={false} - options={options} - onKeyDown={e => e.stopPropagation()} - onChange={this.handleUsersChange} - value={this.selectedUsers} - styles={{ - indicatorSeparator: () => ({ - visibility: "hidden" - }) - }} - /> - <select className="permissions-select" onChange={this.handlePermissionsChange} value={this.permissions}> - {this.sharingOptions(true)} - </select> - <button ref={this.shareDocumentButtonRef} className="share-button" onClick={this.share}> - Share - </button> - </div> - <div className="sort-checkboxes"> - <input type="checkbox" onChange={action(() => this.showUserOptions = !this.showUserOptions)} /> <label style={{ marginRight: 10 }}>Individuals</label> - <input type="checkbox" onChange={action(() => this.showGroupOptions = !this.showGroupOptions)} /> <label>Groups</label> - </div> + { + <div className="share-container"> + <div className="share-setup"> + <Select + className="user-search" + placeholder="Enter user or group name..." + isMulti + isSearchable + closeMenuOnSelect={false} + options={options} + onKeyDown={e => e.stopPropagation()} + onChange={this.handleUsersChange} + value={this.selectedUsers} + styles={{ + indicatorSeparator: () => ({ + visibility: 'hidden', + }), + }} + /> + <select className="permissions-select" onChange={this.handlePermissionsChange} value={this.permissions}> + {this.sharingOptions(true)} + </select> + <button ref={this.shareDocumentButtonRef} className="share-button" onClick={this.share}> + Share + </button> + </div> + <div className="sort-checkboxes"> + <input type="checkbox" onChange={action(() => (this.showUserOptions = !this.showUserOptions))} /> <label style={{ marginRight: 10 }}>Individuals</label> + <input type="checkbox" onChange={action(() => (this.showGroupOptions = !this.showGroupOptions))} /> <label>Groups</label> + </div> - <div className="acl-container"> - {Doc.noviceMode ? (null) : - <div className="layoutDoc-acls"> - <input type="checkbox" onChange={action(() => this.layoutDocAcls = !this.layoutDocAcls)} checked={this.layoutDocAcls} /> <label>Layout</label> - </div>} + <div className="acl-container"> + {Doc.noviceMode ? null : ( + <div className="layoutDoc-acls"> + <input type="checkbox" onChange={action(() => (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} /> <label>Layout</label> + </div> + )} + </div> </div> - </div> } <div className="main-container"> - <div className={"individual-container"}> - <div - className="user-sort" - onClick={action(() => this.individualSort = this.individualSort === "ascending" ? "descending" : this.individualSort === "descending" ? "none" : "ascending")}> - Individuals {this.individualSort === "ascending" ? <FontAwesomeIcon icon={"caret-up"} size={"xs"} /> - : this.individualSort === "descending" ? <FontAwesomeIcon icon={"caret-down"} size={"xs"} /> - : <FontAwesomeIcon icon={"caret-right"} size={"xs"} />} - </div> - <div className={"users-list"}> - {userListContents} + <div className={'individual-container'}> + <div className="user-sort" onClick={action(() => (this.individualSort = this.individualSort === 'ascending' ? 'descending' : this.individualSort === 'descending' ? 'none' : 'ascending'))}> + Individuals{' '} + {this.individualSort === 'ascending' ? ( + <FontAwesomeIcon icon={'caret-up'} size={'xs'} /> + ) : this.individualSort === 'descending' ? ( + <FontAwesomeIcon icon={'caret-down'} size={'xs'} /> + ) : ( + <FontAwesomeIcon icon={'caret-right'} size={'xs'} /> + )} </div> + <div className={'users-list'}>{userListContents}</div> </div> - <div className={"group-container"}> - <div - className="user-sort" - onClick={action(() => this.groupSort = this.groupSort === "ascending" ? "descending" : this.groupSort === "descending" ? "none" : "ascending")}> - Groups {this.groupSort === "ascending" ? <FontAwesomeIcon icon={"caret-up"} size={"xs"} /> - : this.groupSort === "descending" ? <FontAwesomeIcon icon={"caret-down"} size={"xs"} /> - : <FontAwesomeIcon icon={"caret-right"} size={"xs"} />} - - </div> - <div className={"groups-list"}> - {groupListContents} + <div className={'group-container'}> + <div className="user-sort" onClick={action(() => (this.groupSort = this.groupSort === 'ascending' ? 'descending' : this.groupSort === 'descending' ? 'none' : 'ascending'))}> + Groups{' '} + {this.groupSort === 'ascending' ? ( + <FontAwesomeIcon icon={'caret-up'} size={'xs'} /> + ) : this.groupSort === 'descending' ? ( + <FontAwesomeIcon icon={'caret-down'} size={'xs'} /> + ) : ( + <FontAwesomeIcon icon={'caret-right'} size={'xs'} /> + )} </div> + <div className={'groups-list'}>{groupListContents}</div> </div> </div> - </div> </div> ); } render() { - return <MainViewModal - contents={this.sharingInterface} - isDisplayed={this.isOpen} - interactive={true} - dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} - overlayDisplayedOpacity={this.overlayOpacity} - closeOnExternalClick={this.close} - />; + return <MainViewModal contents={this.sharingInterface} isDisplayed={this.isOpen} interactive={true} dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} overlayDisplayedOpacity={this.overlayOpacity} closeOnExternalClick={this.close} />; } -}
\ No newline at end of file +} diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 31fa5b157..542f85228 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -4,42 +4,46 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; -import { AssignAllExtensions } from "../../extensions/General/Extensions"; -import { Docs } from "../documents/Documents"; -import { CurrentUserUtils } from "../util/CurrentUserUtils"; -import { LinkManager } from "../util/LinkManager"; +import { AssignAllExtensions } from '../../extensions/General/Extensions'; +import { Docs } from '../documents/Documents'; +import { CurrentUserUtils } from '../util/CurrentUserUtils'; +import { LinkManager } from '../util/LinkManager'; import { ReplayMovements } from '../util/ReplayMovements'; -import { TrackMovements } from "../util/TrackMovements"; -import { CollectionView } from "./collections/CollectionView"; -import { MainView } from "./MainView"; +import { TrackMovements } from '../util/TrackMovements'; +import { CollectionView } from './collections/CollectionView'; +import { MainView } from './MainView'; AssignAllExtensions(); (async () => { - MainView.Live = window.location.search.includes("live"); - window.location.search.includes("safe") && CollectionView.SetSafeMode(true); + MainView.Live = window.location.search.includes('live'); + window.location.search.includes('safe') && CollectionView.SetSafeMode(true); const info = await CurrentUserUtils.loadCurrentUser(); - if (info.id !== "__guest__") { + if (info.id !== '__guest__') { // a guest will not have an id registered await CurrentUserUtils.loadUserDocument(info.id); } else { await Docs.Prototypes.initialize(); new LinkManager(); } - document.getElementById('root')!.addEventListener('wheel', event => { - if (event.ctrlKey) { - event.preventDefault(); - } - }, true); + document.getElementById('root')!.addEventListener( + 'wheel', + event => { + if (event.ctrlKey) { + event.preventDefault(); + } + }, + true + ); const startload = (document as any).startLoad; - const loading = Date.now() - (startload ? Number(startload) : (Date.now() - 3000)); - console.log("Load Time = " + loading); + const loading = Date.now() - (startload ? Number(startload) : Date.now() - 3000); + console.log('Loading Time = ' + loading); const d = new Date(); - d.setTime(d.getTime() + (100 * 24 * 60 * 60 * 1000)); - const expires = "expires=" + d.toUTCString(); + d.setTime(d.getTime() + 100 * 24 * 60 * 60 * 1000); + const expires = 'expires=' + d.toUTCString(); document.cookie = `loadtime=${loading};${expires};path=/`; new LinkManager(); new TrackMovements(); new ReplayMovements(); ReactDOM.render(<MainView />, document.getElementById('root')); -})();
\ No newline at end of file +})(); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 4940c5f9d..6c0a67de2 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -32,7 +32,7 @@ import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOpti import { CollectionLinearView } from './collections/collectionLinear'; import { CollectionMenu } from './collections/CollectionMenu'; import { CollectionViewType } from './collections/CollectionView'; -import "./collections/TreeView.scss"; +import './collections/TreeView.scss'; import { ComponentDecorations } from './ComponentDecorations'; import { ContextMenu } from './ContextMenu'; import { DashboardView } from './DashboardView'; @@ -45,7 +45,7 @@ import { KeyManager } from './GlobalKeyHandler'; import { InkTranscription } from './InkTranscription'; import { LightboxView } from './LightboxView'; import { LinkMenu } from './linking/LinkMenu'; -import "./MainView.scss"; +import './MainView.scss'; import { AudioBox } from './nodes/AudioBox'; import { DocumentLinksButton } from './nodes/DocumentLinksButton'; import { DocumentView } from './nodes/DocumentView'; @@ -63,7 +63,7 @@ import { PreviewCursor } from './PreviewCursor'; import { PropertiesView } from './PropertiesView'; import { DashboardStyleProvider, DefaultStyleProvider } from './StyleProvider'; import { TopBar } from './topbar/TopBar'; -const _global = (window /* browser */ || global /* node */) as any; +const _global = (window /* browser */ || global) /* node */ as any; @observer export class MainView extends React.Component { @@ -73,30 +73,54 @@ export class MainView extends React.Component { @observable public LastButton: Opt<Doc>; @observable private _windowWidth: number = 0; @observable private _windowHeight: number = 0; - @observable private _dashUIWidth: number = 0; // width of entire main dashboard region including left menu buttons and properties panel (but not including the dashboard selector button row) - @observable private _dashUIHeight: number = 0; // height of entire main dashboard region including top menu buttons - @observable private _panelContent: string = "none"; + @observable private _dashUIWidth: number = 0; // width of entire main dashboard region including left menu buttons and properties panel (but not including the dashboard selector button row) + @observable private _dashUIHeight: number = 0; // height of entire main dashboard region including top menu buttons + @observable private _panelContent: string = 'none'; @observable private _sidebarContent: any = CurrentUserUtils.MyLeftSidebarPanel; @observable private _leftMenuFlyoutWidth: number = 0; - @computed private get dashboardTabHeight() { return 27; } // 27 comes form lm.config.defaultConfig.dimensions.headerHeight in goldenlayout.js - @computed private get topOfDashUI() { return Number(DASHBOARD_SELECTOR_HEIGHT.replace("px", "")); } - @computed private get topOfHeaderBarDoc() { return this.topOfDashUI; } - @computed private get topOfSidebarDoc() { return this.topOfDashUI + this.topMenuHeight(); } - @computed private get topOfMainDoc() { return this.topOfDashUI + this.topMenuHeight() + this.headerBarDocHeight(); } - @computed private get topOfMainDocContent() { return this.topOfMainDoc + this.dashboardTabHeight; } - @computed private get leftScreenOffsetOfMainDocView() { return this.leftMenuWidth() - 2; } - @computed private get userDoc() { return Doc.UserDoc(); } - @computed private get colorScheme() { return StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme); } - @computed private get mainContainer() { return this.userDoc ? CurrentUserUtils.ActiveDashboard : CurrentUserUtils.GuestDashboard; } - @computed private get headerBarDoc() { return CurrentUserUtils.MyHeaderBar; } - @computed public get mainFreeform(): Opt<Doc> { return (docs => (docs?.length > 1) ? docs[1] : undefined)(DocListCast(this.mainContainer!.data)); } + @computed private get dashboardTabHeight() { + return 27; + } // 27 comes form lm.config.defaultConfig.dimensions.headerHeight in goldenlayout.js + @computed private get topOfDashUI() { + return Number(DASHBOARD_SELECTOR_HEIGHT.replace('px', '')); + } + @computed private get topOfHeaderBarDoc() { + return this.topOfDashUI; + } + @computed private get topOfSidebarDoc() { + return this.topOfDashUI + this.topMenuHeight(); + } + @computed private get topOfMainDoc() { + return this.topOfDashUI + this.topMenuHeight() + this.headerBarDocHeight(); + } + @computed private get topOfMainDocContent() { + return this.topOfMainDoc + this.dashboardTabHeight; + } + @computed private get leftScreenOffsetOfMainDocView() { + return this.leftMenuWidth() - 2; + } + @computed private get userDoc() { + return Doc.UserDoc(); + } + @computed private get colorScheme() { + return StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme); + } + @computed private get mainContainer() { + return this.userDoc ? CurrentUserUtils.ActiveDashboard : CurrentUserUtils.GuestDashboard; + } + @computed private get headerBarDoc() { + return CurrentUserUtils.MyHeaderBar; + } + @computed public get mainFreeform(): Opt<Doc> { + return (docs => (docs?.length > 1 ? docs[1] : undefined))(DocListCast(this.mainContainer!.data)); + } headerBarDocWidth = () => this.mainDocViewWidth(); headerBarDocHeight = () => CurrentUserUtils.headerBarHeight ?? 0; topMenuHeight = () => 35; topMenuWidth = returnZero; // value is ignored ... - leftMenuWidth = () => Number(LEFT_MENU_WIDTH.replace("px", "")); + leftMenuWidth = () => Number(LEFT_MENU_WIDTH.replace('px', '')); leftMenuHeight = () => this._dashUIHeight; leftMenuFlyoutWidth = () => this._leftMenuFlyoutWidth; leftMenuFlyoutHeight = () => this._dashUIHeight; @@ -106,89 +130,325 @@ export class MainView extends React.Component { mainDocViewHeight = () => this._dashUIHeight - this.headerBarDocHeight(); componentDidMount() { - document.getElementById("root")?.addEventListener("scroll", e => ((ele) => ele.scrollLeft = ele.scrollTop = 0)(document.getElementById("root")!)); - const ele = document.getElementById("loader"); - const prog = document.getElementById("dash-progress"); + document.getElementById('root')?.addEventListener('scroll', e => (ele => (ele.scrollLeft = ele.scrollTop = 0))(document.getElementById('root')!)); + const ele = document.getElementById('loader'); + const prog = document.getElementById('dash-progress'); if (ele && prog) { // remove from DOM setTimeout(() => { clearTimeout(); - prog.style.transition = "1s"; - prog.style.width = "100%"; + prog.style.transition = '1s'; + prog.style.width = '100%'; }, 0); - setTimeout(() => ele.outerHTML = '', 1000); + setTimeout(() => (ele.outerHTML = ''), 1000); } this._sidebarContent.proto = undefined; if (!MainView.Live) { - DocServer.setPlaygroundFields(["dataTransition", "treeViewOpen", "showSidebar", "sidebarWidthPercent", "viewTransition", - "panX", "panY", "nativeWidth", "nativeHeight", "text-scrollHeight", "text-height", "hideMinimap", - "viewScale", "scrollTop", "hidden", "curPage", "viewType", "chromeHidden", "nativeWidth"]); // can play with these fields on someone else's + DocServer.setPlaygroundFields([ + 'dataTransition', + 'treeViewOpen', + 'showSidebar', + 'sidebarWidthPercent', + 'viewTransition', + 'panX', + 'panY', + 'nativeWidth', + 'nativeHeight', + 'text-scrollHeight', + 'text-height', + 'hideMinimap', + 'viewScale', + 'scrollTop', + 'hidden', + 'curPage', + 'viewType', + 'chromeHidden', + 'nativeWidth', + ]); // can play with these fields on someone else's } - DocServer.GetRefField("rtfProto").then(proto => (proto instanceof Doc) && reaction(() => StrCast(proto.BROADCAST_MESSAGE), msg => msg && alert(msg))); + DocServer.GetRefField('rtfProto').then( + proto => + proto instanceof Doc && + reaction( + () => StrCast(proto.BROADCAST_MESSAGE), + msg => msg && alert(msg) + ) + ); const tag = document.createElement('script'); - tag.src = "https://www.youtube.com/iframe_api"; + tag.src = 'https://www.youtube.com/iframe_api'; const firstScriptTag = document.getElementsByTagName('script')[0]; firstScriptTag.parentNode!.insertBefore(tag, firstScriptTag); - window.removeEventListener("keydown", KeyManager.Instance.handle); - window.addEventListener("keydown", KeyManager.Instance.handle); - window.removeEventListener("keyup", KeyManager.Instance.unhandle); - window.addEventListener("keyup", KeyManager.Instance.unhandle); - window.addEventListener("paste", KeyManager.Instance.paste as any); - document.addEventListener("dash", (e: any) => { // event used by chrome plugin to tell Dash which document to focus on + window.removeEventListener('keydown', KeyManager.Instance.handle); + window.addEventListener('keydown', KeyManager.Instance.handle); + window.removeEventListener('keyup', KeyManager.Instance.unhandle); + window.addEventListener('keyup', KeyManager.Instance.unhandle); + window.addEventListener('paste', KeyManager.Instance.paste as any); + document.addEventListener('dash', (e: any) => { + // event used by chrome plugin to tell Dash which document to focus on const id = FormattedTextBox.GetDocFromUrl(e.detail); - DocServer.GetRefField(id).then(doc => (doc instanceof Doc) ? DocumentManager.Instance.jumpToDocument(doc, false, undefined, []) : (null)); + DocServer.GetRefField(id).then(doc => (doc instanceof Doc ? DocumentManager.Instance.jumpToDocument(doc, false, undefined, []) : null)); }); - document.addEventListener("linkAnnotationToDash", Hypothesis.linkListener); + document.addEventListener('linkAnnotationToDash', Hypothesis.linkListener); this.initEventListeners(); } componentWillUnMount() { - window.removeEventListener("keyup", KeyManager.Instance.unhandle); - window.removeEventListener("keydown", KeyManager.Instance.handle); - window.removeEventListener("pointerdown", this.globalPointerDown); - window.removeEventListener("paste", KeyManager.Instance.paste as any); - document.removeEventListener("linkAnnotationToDash", Hypothesis.linkListener); + window.removeEventListener('keyup', KeyManager.Instance.unhandle); + window.removeEventListener('keydown', KeyManager.Instance.handle); + window.removeEventListener('pointerdown', this.globalPointerDown); + window.removeEventListener('paste', KeyManager.Instance.paste as any); + document.removeEventListener('linkAnnotationToDash', Hypothesis.linkListener); } constructor(props: Readonly<{}>) { super(props); MainView.Instance = this; - CurrentUserUtils._urlState = HistoryUtil.parseUrl(window.location) || {} as any; + CurrentUserUtils._urlState = HistoryUtil.parseUrl(window.location) || ({} as any); // causes errors to be generated when modifying an observable outside of an action - configure({ enforceActions: "observed" }); + configure({ enforceActions: 'observed' }); - if (window.location.pathname !== "/home") { - const pathname = window.location.pathname.substr(1).split("/"); - if (pathname.length > 1 && pathname[0] === "doc") { + if (window.location.pathname !== '/home') { + const pathname = window.location.pathname.substr(1).split('/'); + if (pathname.length > 1 && pathname[0] === 'doc') { CurrentUserUtils.MainDocId = pathname[1]; !this.userDoc && DocServer.GetRefField(pathname[1]).then(action(field => field instanceof Doc && (CurrentUserUtils.GuestTarget = field))); } } - library.add(...[fa.faEdit, fa.faTrash, fa.faTrashAlt, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faCalendar, - fa.faSquare, far.faSquare as any, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faMapMarker, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, - fa.faLock, fa.faLaptopCode, fa.faMale, fa.faCopy, fa.faHandPointLeft, fa.faHandPointRight, fa.faCompass, fa.faSnowflake, fa.faMicrophone, fa.faKeyboard, - fa.faQuestion, fa.faTasks, fa.faPalette, fa.faAngleLeft, fa.faAngleRight, fa.faBell, fa.faCamera, fa.faExpand, fa.faCaretDown, fa.faCaretLeft, fa.faCaretRight, - fa.faCaretSquareDown, fa.faCaretSquareRight, fa.faArrowsAltH, fa.faPlus, fa.faMinus, fa.faTerminal, fa.faToggleOn, fa.faFile, fa.faLocationArrow, - fa.faSearch, fa.faFileDownload, fa.faFileUpload, fa.faStop, fa.faCalculator, fa.faWindowMaximize, fa.faAddressCard, fa.faQuestionCircle, fa.faArrowLeft, - fa.faArrowRight, fa.faArrowDown, fa.faArrowUp, fa.faBolt, fa.faBullseye, fa.faCaretUp, fa.faCat, fa.faCheck, fa.faChevronRight, fa.faChevronLeft, fa.faChevronDown, fa.faChevronUp, - fa.faClone, fa.faCloudUploadAlt, fa.faCommentAlt, fa.faCompressArrowsAlt, fa.faCut, fa.faEllipsisV, fa.faEraser, fa.faExclamation, fa.faFileAlt, - fa.faFileAudio, fa.faFileVideo, fa.faFilePdf, fa.faFilm, fa.faFilter, fa.faFont, fa.faGlobeAmericas, fa.faGlobeAsia, fa.faHighlighter, fa.faLongArrowAltRight, fa.faMousePointer, - fa.faMusic, fa.faObjectGroup, fa.faPause, fa.faPen, fa.faPenNib, fa.faPhone, fa.faPlay, fa.faPortrait, fa.faRedoAlt, fa.faStamp, fa.faStickyNote, fa.faArrowsAltV, - fa.faTimesCircle, fa.faThumbtack, fa.faTree, fa.faTv, fa.faUndoAlt, fa.faVideoSlash, fa.faVideo, fa.faAsterisk, fa.faBrain, fa.faImage, fa.faPaintBrush, fa.faTimes, fa.faFlag, - fa.faEye, fa.faArrowsAlt, fa.faQuoteLeft, fa.faSortAmountDown, fa.faAlignLeft, fa.faAlignCenter, fa.faAlignRight, fa.faHeading, fa.faRulerCombined, - fa.faFillDrip, fa.faLink, fa.faUnlink, fa.faBold, fa.faItalic, fa.faClipboard, fa.faUnderline, fa.faStrikethrough, fa.faSuperscript, fa.faSubscript, - fa.faIndent, fa.faEyeDropper, fa.faPaintRoller, fa.faBars, fa.faBrush, fa.faShapes, fa.faEllipsisH, fa.faHandPaper, fa.faMap, fa.faUser, faHireAHelper as any, - fa.faTrashRestore, fa.faUsers, fa.faWrench, fa.faCog, fa.faMap, fa.faBellSlash, fa.faExpandAlt, fa.faArchive, fa.faBezierCurve, fa.faCircle, far.faCircle as any, - fa.faLongArrowAltRight, fa.faPenFancy, fa.faAngleDoubleRight, fa.faAngleDoubleDown, fa.faAngleDoubleLeft, fa.faAngleDoubleUp, faBuffer as any, fa.faExpand, fa.faUndo, - fa.faSlidersH, fa.faAngleUp, fa.faAngleDown, fa.faPlayCircle, fa.faClock, fa.faRocket, fa.faExchangeAlt, fa.faHashtag, fa.faAlignJustify, fa.faCheckSquare, fa.faListUl, - fa.faWindowMinimize, fa.faWindowRestore, fa.faTextWidth, fa.faTextHeight, fa.faClosedCaptioning, fa.faInfoCircle, fa.faTag, fa.faSyncAlt, fa.faPhotoVideo, - fa.faArrowAltCircleDown, fa.faArrowAltCircleUp, fa.faArrowAltCircleLeft, fa.faArrowAltCircleRight, fa.faStopCircle, fa.faCheckCircle, fa.faGripVertical, - fa.faSortUp, fa.faSortDown, fa.faTable, fa.faTh, fa.faThList, fa.faProjectDiagram, fa.faSignature, fa.faColumns, fa.faChevronCircleUp, fa.faUpload, fa.faBorderAll, - fa.faBraille, fa.faChalkboard, fa.faPencilAlt, fa.faEyeSlash, fa.faSmile, fa.faIndent, fa.faOutdent, fa.faChartBar, fa.faBan, fa.faPhoneSlash, fa.faGripLines, - fa.faSave, fa.faBookmark, fa.faList, fa.faListOl, fa.faFolderPlus, fa.faLightbulb, fa.faBookOpen, fa.faMapMarkerAlt, fa.faSearchPlus, fa.faVolumeUp, fa.faVolumeDown, fa.faSquareRootAlt, fa.faVolumeMute]); + library.add( + ...[ + fa.faEdit, + fa.faTrash, + fa.faTrashAlt, + fa.faShare, + fa.faDownload, + fa.faExpandArrowsAlt, + fa.faLayerGroup, + fa.faExternalLinkAlt, + fa.faCalendar, + fa.faSquare, + far.faSquare as any, + fa.faConciergeBell, + fa.faWindowRestore, + fa.faFolder, + fa.faMapPin, + fa.faMapMarker, + fa.faFingerprint, + fa.faCrosshairs, + fa.faDesktop, + fa.faUnlock, + fa.faLock, + fa.faLaptopCode, + fa.faMale, + fa.faCopy, + fa.faHandPointLeft, + fa.faHandPointRight, + fa.faCompass, + fa.faSnowflake, + fa.faMicrophone, + fa.faKeyboard, + fa.faQuestion, + fa.faTasks, + fa.faPalette, + fa.faAngleLeft, + fa.faAngleRight, + fa.faBell, + fa.faCamera, + fa.faExpand, + fa.faCaretDown, + fa.faCaretLeft, + fa.faCaretRight, + fa.faCaretSquareDown, + fa.faCaretSquareRight, + fa.faArrowsAltH, + fa.faPlus, + fa.faMinus, + fa.faTerminal, + fa.faToggleOn, + fa.faFile, + fa.faLocationArrow, + fa.faSearch, + fa.faFileDownload, + fa.faFileUpload, + fa.faStop, + fa.faCalculator, + fa.faWindowMaximize, + fa.faAddressCard, + fa.faQuestionCircle, + fa.faArrowLeft, + fa.faArrowRight, + fa.faArrowDown, + fa.faArrowUp, + fa.faBolt, + fa.faBullseye, + fa.faCaretUp, + fa.faCat, + fa.faCheck, + fa.faChevronRight, + fa.faChevronLeft, + fa.faChevronDown, + fa.faChevronUp, + fa.faClone, + fa.faCloudUploadAlt, + fa.faCommentAlt, + fa.faCompressArrowsAlt, + fa.faCut, + fa.faEllipsisV, + fa.faEraser, + fa.faExclamation, + fa.faFileAlt, + fa.faFileAudio, + fa.faFileVideo, + fa.faFilePdf, + fa.faFilm, + fa.faFilter, + fa.faFont, + fa.faGlobeAmericas, + fa.faGlobeAsia, + fa.faHighlighter, + fa.faLongArrowAltRight, + fa.faMousePointer, + fa.faMusic, + fa.faObjectGroup, + fa.faPause, + fa.faPen, + fa.faPenNib, + fa.faPhone, + fa.faPlay, + fa.faPortrait, + fa.faRedoAlt, + fa.faStamp, + fa.faStickyNote, + fa.faArrowsAltV, + fa.faTimesCircle, + fa.faThumbtack, + fa.faTree, + fa.faTv, + fa.faUndoAlt, + fa.faVideoSlash, + fa.faVideo, + fa.faAsterisk, + fa.faBrain, + fa.faImage, + fa.faPaintBrush, + fa.faTimes, + fa.faFlag, + fa.faEye, + fa.faArrowsAlt, + fa.faQuoteLeft, + fa.faSortAmountDown, + fa.faAlignLeft, + fa.faAlignCenter, + fa.faAlignRight, + fa.faHeading, + fa.faRulerCombined, + fa.faFillDrip, + fa.faLink, + fa.faUnlink, + fa.faBold, + fa.faItalic, + fa.faClipboard, + fa.faUnderline, + fa.faStrikethrough, + fa.faSuperscript, + fa.faSubscript, + fa.faIndent, + fa.faEyeDropper, + fa.faPaintRoller, + fa.faBars, + fa.faBrush, + fa.faShapes, + fa.faEllipsisH, + fa.faHandPaper, + fa.faMap, + fa.faUser, + faHireAHelper as any, + fa.faTrashRestore, + fa.faUsers, + fa.faWrench, + fa.faCog, + fa.faMap, + fa.faBellSlash, + fa.faExpandAlt, + fa.faArchive, + fa.faBezierCurve, + fa.faCircle, + far.faCircle as any, + fa.faLongArrowAltRight, + fa.faPenFancy, + fa.faAngleDoubleRight, + fa.faAngleDoubleDown, + fa.faAngleDoubleLeft, + fa.faAngleDoubleUp, + faBuffer as any, + fa.faExpand, + fa.faUndo, + fa.faSlidersH, + fa.faAngleUp, + fa.faAngleDown, + fa.faPlayCircle, + fa.faClock, + fa.faRocket, + fa.faExchangeAlt, + fa.faHashtag, + fa.faAlignJustify, + fa.faCheckSquare, + fa.faListUl, + fa.faWindowMinimize, + fa.faWindowRestore, + fa.faTextWidth, + fa.faTextHeight, + fa.faClosedCaptioning, + fa.faInfoCircle, + fa.faTag, + fa.faSyncAlt, + fa.faPhotoVideo, + fa.faArrowAltCircleDown, + fa.faArrowAltCircleUp, + fa.faArrowAltCircleLeft, + fa.faArrowAltCircleRight, + fa.faStopCircle, + fa.faCheckCircle, + fa.faGripVertical, + fa.faSortUp, + fa.faSortDown, + fa.faTable, + fa.faTh, + fa.faThList, + fa.faProjectDiagram, + fa.faSignature, + fa.faColumns, + fa.faChevronCircleUp, + fa.faUpload, + fa.faBorderAll, + fa.faBraille, + fa.faChalkboard, + fa.faPencilAlt, + fa.faEyeSlash, + fa.faSmile, + fa.faIndent, + fa.faOutdent, + fa.faChartBar, + fa.faBan, + fa.faPhoneSlash, + fa.faGripLines, + fa.faSave, + fa.faBookmark, + fa.faList, + fa.faListOl, + fa.faFolderPlus, + fa.faLightbulb, + fa.faBookOpen, + fa.faMapMarkerAlt, + fa.faSearchPlus, + fa.faVolumeUp, + fa.faVolumeDown, + fa.faSquareRootAlt, + fa.faVolumeMute, + ] + ); this.initAuthenticationRouters(); } @@ -197,175 +457,211 @@ export class MainView extends React.Component { const targets = document.elementsFromPoint(e.x, e.y); if (targets.length) { const targClass = targets[0].className.toString(); - !targClass.includes("contextMenu") && ContextMenu.Instance.closeMenu(); - !["timeline-menu-desc", "timeline-menu-item", "timeline-menu-input"].includes(targClass) && TimelineMenu.Instance.closeMenu(); + !targClass.includes('contextMenu') && ContextMenu.Instance.closeMenu(); + !['timeline-menu-desc', 'timeline-menu-item', 'timeline-menu-input'].includes(targClass) && TimelineMenu.Instance.closeMenu(); } }); initEventListeners = () => { - window.addEventListener("drop", e => e.preventDefault(), false); // prevent default behavior of navigating to a new web page - window.addEventListener("dragover", e => e.preventDefault(), false); + window.addEventListener('drop', e => e.preventDefault(), false); // prevent default behavior of navigating to a new web page + window.addEventListener('dragover', e => e.preventDefault(), false); // document.addEventListener("pointermove", action(e => SearchBox.Instance._undoBackground = UndoManager.batchCounter ? "#000000a8" : undefined)); - document.addEventListener("pointerdown", this.globalPointerDown); - document.addEventListener("click", (e: MouseEvent) => { - if (!e.cancelBubble) { - const pathstr = (e as any)?.path?.map((p: any) => p.classList?.toString()).join(); - if (pathstr?.includes("libraryFlyout")) { - SelectionManager.DeselectAll(); + document.addEventListener('pointerdown', this.globalPointerDown); + document.addEventListener( + 'click', + (e: MouseEvent) => { + if (!e.cancelBubble) { + const pathstr = (e as any)?.path?.map((p: any) => p.classList?.toString()).join(); + if (pathstr?.includes('libraryFlyout')) { + SelectionManager.DeselectAll(); + } } - } - }, false); + }, + false + ); document.oncontextmenu = () => false; - } + }; initAuthenticationRouters = async () => { const received = CurrentUserUtils.MainDocId; if (received && !this.userDoc) { - reaction(() => CurrentUserUtils.GuestTarget, target => target && CurrentUserUtils.createNewDashboard(), { fireImmediately: true }); - } + reaction( + () => CurrentUserUtils.GuestTarget, + target => target && CurrentUserUtils.createNewDashboard(), + { fireImmediately: true } + ); + } // else { // PromiseValue(this.userDoc.activeDashboard).then(dash => { // if (dash instanceof Doc) CurrentUserUtils.openDashboard(dash); // else CurrentUserUtils.createNewDashboard(); // }); // } - } + }; @action createNewPresentation = async () => { - const pres = Docs.Create.PresDocument({ title: "Untitled Trail", _viewType: CollectionViewType.Stacking, _fitWidth: true, _width: 400, _height: 500, targetDropAction: "alias", _chromeHidden: true, boxShadow: "0 0" }); - CollectionDockingView.AddSplit(pres, "left"); + const pres = Docs.Create.PresDocument({ title: 'Untitled Trail', _viewType: CollectionViewType.Stacking, _fitWidth: true, _width: 400, _height: 500, targetDropAction: 'alias', _chromeHidden: true, boxShadow: '0 0' }); + CollectionDockingView.AddSplit(pres, 'left'); CurrentUserUtils.ActivePresentation = pres; - Doc.AddDocToList(CurrentUserUtils.MyTrails, "data", pres); - } + Doc.AddDocToList(CurrentUserUtils.MyTrails, 'data', pres); + }; @action createNewFolder = async () => { - const folder = Docs.Create.TreeDocument([], { title: "Untitled folder", _stayInCollection: true, isFolder: true }); - Doc.AddDocToList(CurrentUserUtils.MyFilesystem, "data", folder); - } + const folder = Docs.Create.TreeDocument([], { title: 'Untitled folder', _stayInCollection: true, isFolder: true }); + Doc.AddDocToList(CurrentUserUtils.MyFilesystem, 'data', folder); + }; @observable _exploreMode = false; @computed get exploreMode() { - return () => this._exploreMode ? ScriptField.MakeScript("CollectionBrowseClick(documentView, clientX, clientY)", - { documentView: "any", clientX: "number", clientY: "number" })! : undefined; + return () => (this._exploreMode ? ScriptField.MakeScript('CollectionBrowseClick(documentView, clientX, clientY)', { documentView: 'any', clientX: 'number', clientY: 'number' })! : undefined); } headerBarScreenXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.headerBarDocHeight(), 1); @computed get headerBarDocView() { - return <div className="mainView-headerBar" style={{ height: this.headerBarDocHeight() }}> - <DocumentView key="headerBarDoc" - Document={this.headerBarDoc} - DataDoc={undefined} - addDocument={undefined} - addDocTab={this.addDocTabFunc} - pinToPres={emptyFunction} - docViewPath={returnEmptyDoclist} - styleProvider={DefaultStyleProvider} - rootSelected={returnTrue} - removeDocument={returnFalse} - fitContentsToBox={returnTrue} - isDocumentActive={returnTrue} // headerBar is always documentActive (ie, the docView gets pointer events) - isContentActive={returnTrue} // headerBar is awlays contentActive which means its items are always documentActive - ScreenToLocalTransform={this.headerBarScreenXf} - childHideResizeHandles={returnTrue} - dontRegisterView={true} - hideResizeHandles={true} - PanelWidth={this.headerBarDocWidth} - PanelHeight={this.headerBarDocHeight} - renderDepth={0} - focus={DocUtils.DefaultFocus} - whenChildContentsActiveChanged={emptyFunction} - bringToFront={emptyFunction} - docFilters={returnEmptyFilter} - docRangeFilters={returnEmptyFilter} - searchFilterDocs={returnEmptyDoclist} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} - /></div>; + return ( + <div className="mainView-headerBar" style={{ height: this.headerBarDocHeight() }}> + <DocumentView + key="headerBarDoc" + Document={this.headerBarDoc} + DataDoc={undefined} + addDocument={undefined} + addDocTab={this.addDocTabFunc} + pinToPres={emptyFunction} + docViewPath={returnEmptyDoclist} + styleProvider={DefaultStyleProvider} + rootSelected={returnTrue} + removeDocument={returnFalse} + fitContentsToBox={returnTrue} + isDocumentActive={returnTrue} // headerBar is always documentActive (ie, the docView gets pointer events) + isContentActive={returnTrue} // headerBar is awlays contentActive which means its items are always documentActive + ScreenToLocalTransform={this.headerBarScreenXf} + childHideResizeHandles={returnTrue} + dontRegisterView={true} + hideResizeHandles={true} + PanelWidth={this.headerBarDocWidth} + PanelHeight={this.headerBarDocHeight} + renderDepth={0} + focus={DocUtils.DefaultFocus} + whenChildContentsActiveChanged={emptyFunction} + bringToFront={emptyFunction} + docFilters={returnEmptyFilter} + docRangeFilters={returnEmptyFilter} + searchFilterDocs={returnEmptyDoclist} + ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} + /> + </div> + ); } @computed get mainDocView() { - return <> - {this.headerBarDocView} - <DocumentView key="main" - Document={this.mainContainer!} - DataDoc={undefined} - addDocument={undefined} - addDocTab={this.addDocTabFunc} - pinToPres={emptyFunction} - docViewPath={returnEmptyDoclist} - styleProvider={undefined} - rootSelected={returnTrue} - isContentActive={returnTrue} - removeDocument={undefined} - ScreenToLocalTransform={Transform.Identity} - PanelWidth={this.mainDocViewWidth} - PanelHeight={this.mainDocViewHeight} - focus={DocUtils.DefaultFocus} - whenChildContentsActiveChanged={emptyFunction} - bringToFront={emptyFunction} - docFilters={returnEmptyFilter} - docRangeFilters={returnEmptyFilter} - searchFilterDocs={returnEmptyDoclist} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} - suppressSetHeight={true} - renderDepth={-1} - /></>; + return ( + <> + {this.headerBarDocView} + <DocumentView + key="main" + Document={this.mainContainer!} + DataDoc={undefined} + addDocument={undefined} + addDocTab={this.addDocTabFunc} + pinToPres={emptyFunction} + docViewPath={returnEmptyDoclist} + styleProvider={undefined} + rootSelected={returnTrue} + isContentActive={returnTrue} + removeDocument={undefined} + ScreenToLocalTransform={Transform.Identity} + PanelWidth={this.mainDocViewWidth} + PanelHeight={this.mainDocViewHeight} + focus={DocUtils.DefaultFocus} + whenChildContentsActiveChanged={emptyFunction} + bringToFront={emptyFunction} + docFilters={returnEmptyFilter} + docRangeFilters={returnEmptyFilter} + searchFilterDocs={returnEmptyDoclist} + ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} + suppressSetHeight={true} + renderDepth={-1} + /> + </> + ); } @computed get dockingContent() { - return <div key="docking" className={`mainView-dockingContent${this._leftMenuFlyoutWidth ? "-flyout" : ""}`} onDrop={e => { e.stopPropagation(); e.preventDefault(); }} - style={{ - minWidth: `calc(100% - ${this._leftMenuFlyoutWidth + this.leftMenuWidth() + this.propertiesWidth()}px)`, - transform: LightboxView.LightboxDoc ? "scale(0.0001)" : undefined, - }}> - {!this.mainContainer ? (null) : this.mainDocView} - </div>; + return ( + <div + key="docking" + className={`mainView-dockingContent${this._leftMenuFlyoutWidth ? '-flyout' : ''}`} + onDrop={e => { + e.stopPropagation(); + e.preventDefault(); + }} + style={{ + minWidth: `calc(100% - ${this._leftMenuFlyoutWidth + this.leftMenuWidth() + this.propertiesWidth()}px)`, + transform: LightboxView.LightboxDoc ? 'scale(0.0001)' : undefined, + }}> + {!this.mainContainer ? null : this.mainDocView} + </div> + ); } @action onPropertiesPointerDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, - action(e => (CurrentUserUtils.propertiesWidth = Math.max(0, this._dashUIWidth - e.clientX)) ? false : false), + setupMoveUpEvents( + this, + e, + action(e => ((CurrentUserUtils.propertiesWidth = Math.max(0, this._dashUIWidth - e.clientX)) ? false : false)), action(() => CurrentUserUtils.propertiesWidth < 5 && (CurrentUserUtils.propertiesWidth = 0)), - action(() => CurrentUserUtils.propertiesWidth = this.propertiesWidth() < 15 ? Math.min(this._dashUIWidth - 50, 250) : 0), false); - } + action(() => (CurrentUserUtils.propertiesWidth = this.propertiesWidth() < 15 ? Math.min(this._dashUIWidth - 50, 250) : 0)), + false + ); + }; @action onFlyoutPointerDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, - action(e => (this._leftMenuFlyoutWidth = Math.max(e.clientX - 58, 0)) ? false : false), + setupMoveUpEvents( + this, + e, + action(e => ((this._leftMenuFlyoutWidth = Math.max(e.clientX - 58, 0)) ? false : false)), () => this._leftMenuFlyoutWidth < 5 && this.closeFlyout(), - this.closeFlyout); - } + this.closeFlyout + ); + }; sidebarScreenToLocal = () => new Transform(0, -this.topOfSidebarDoc, 1); mainContainerXf = () => this.sidebarScreenToLocal().translate(-this.leftScreenOffsetOfMainDocView, 0); addDocTabFunc = (doc: Doc, location: string): boolean => { - const locationFields = doc._viewType === CollectionViewType.Docking ? ["dashboard"] : location.split(":"); - const locationParams = locationFields.length > 1 ? locationFields[1] : ""; + const locationFields = doc._viewType === CollectionViewType.Docking ? ['dashboard'] : location.split(':'); + const locationParams = locationFields.length > 1 ? locationFields[1] : ''; if (doc.dockingConfig) return CurrentUserUtils.openDashboard(doc); switch (locationFields[0]) { - case "dashboard": return CurrentUserUtils.openDashboard(doc); - case "close": return CollectionDockingView.CloseSplit(doc, locationParams); - case "fullScreen": return CollectionDockingView.OpenFullScreen(doc); - case "lightbox": return LightboxView.AddDocTab(doc, location); - case "toggle": return CollectionDockingView.ToggleSplit(doc, locationParams); - case "inPlace": - case "add": + case 'dashboard': + return CurrentUserUtils.openDashboard(doc); + case 'close': + return CollectionDockingView.CloseSplit(doc, locationParams); + case 'fullScreen': + return CollectionDockingView.OpenFullScreen(doc); + case 'lightbox': + return LightboxView.AddDocTab(doc, location); + case 'toggle': + return CollectionDockingView.ToggleSplit(doc, locationParams); + case 'inPlace': + case 'add': default: return CollectionDockingView.AddSplit(doc, locationParams); } - } - + }; @computed get flyout() { - return !this._leftMenuFlyoutWidth ? <div key="flyout" className={`mainView-libraryFlyout-out`}> - {this.docButtons} - </div> : - <div key="libFlyout" className="mainView-libraryFlyout" style={{ minWidth: this._leftMenuFlyoutWidth, width: this._leftMenuFlyoutWidth }} > - <div className="mainView-contentArea" > + return !this._leftMenuFlyoutWidth ? ( + <div key="flyout" className={`mainView-libraryFlyout-out`}> + {this.docButtons} + </div> + ) : ( + <div key="libFlyout" className="mainView-libraryFlyout" style={{ minWidth: this._leftMenuFlyoutWidth, width: this._leftMenuFlyoutWidth }}> + <div className="mainView-contentArea"> <DocumentView Document={this._sidebarContent.proto || this._sidebarContent} DataDoc={undefined} @@ -393,37 +689,40 @@ export class MainView extends React.Component { /> </div> {this.docButtons} - </div>; + </div> + ); } @computed get leftMenuPanel() { - return <div key="menu" className="mainView-leftMenuPanel"> - <DocumentView - Document={CurrentUserUtils.MyLeftSidebarMenu} - DataDoc={undefined} - addDocument={undefined} - addDocTab={this.addDocTabFunc} - pinToPres={emptyFunction} - rootSelected={returnTrue} - removeDocument={returnFalse} - ScreenToLocalTransform={this.sidebarScreenToLocal} - PanelWidth={this.leftMenuWidth} - PanelHeight={this.leftMenuHeight} - renderDepth={0} - docViewPath={returnEmptyDoclist} - focus={DocUtils.DefaultFocus} - styleProvider={DefaultStyleProvider} - isContentActive={returnTrue} - whenChildContentsActiveChanged={emptyFunction} - bringToFront={emptyFunction} - docFilters={returnEmptyFilter} - docRangeFilters={returnEmptyFilter} - searchFilterDocs={returnEmptyDoclist} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} - scriptContext={this} - /> - </div>; + return ( + <div key="menu" className="mainView-leftMenuPanel"> + <DocumentView + Document={CurrentUserUtils.MyLeftSidebarMenu} + DataDoc={undefined} + addDocument={undefined} + addDocTab={this.addDocTabFunc} + pinToPres={emptyFunction} + rootSelected={returnTrue} + removeDocument={returnFalse} + ScreenToLocalTransform={this.sidebarScreenToLocal} + PanelWidth={this.leftMenuWidth} + PanelHeight={this.leftMenuHeight} + renderDepth={0} + docViewPath={returnEmptyDoclist} + focus={DocUtils.DefaultFocus} + styleProvider={DefaultStyleProvider} + isContentActive={returnTrue} + whenChildContentsActiveChanged={emptyFunction} + bringToFront={emptyFunction} + docFilters={returnEmptyFilter} + docRangeFilters={returnEmptyFilter} + searchFilterDocs={returnEmptyDoclist} + ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} + scriptContext={this} + /> + </div> + ); } @action @@ -432,69 +731,78 @@ export class MainView extends React.Component { const willOpen = !this._leftMenuFlyoutWidth || this._panelContent !== title; this.closeFlyout(); if (willOpen) { - switch (this._panelContent = title) { - case "Settings": + switch ((this._panelContent = title)) { + case 'Settings': SettingsManager.Instance.open(); break; - case "Help": + case 'Help': break; default: this.expandFlyout(button); } } return true; - } + }; @computed get mainInnerContent() { const leftMenuFlyoutWidth = this._leftMenuFlyoutWidth + this.leftMenuWidth(); const width = this.propertiesWidth() + leftMenuFlyoutWidth; - return <> - {this.leftMenuPanel} - <div key="inner" className={`mainView-innerContent${this.colorScheme}`}> - {this.flyout} - <div className="mainView-libraryHandle" style={{ left: leftMenuFlyoutWidth - 10 /* ~half width of handle */, display: !this._leftMenuFlyoutWidth ? "none" : undefined }} onPointerDown={this.onFlyoutPointerDown} > - <FontAwesomeIcon icon="chevron-left" color={this.colorScheme === ColorScheme.Dark ? "white" : "black"} style={{ opacity: "50%" }} size="sm" /> - </div> - <div className="mainView-innerContainer" style={{ width: `calc(100% - ${width}px)` }}> - - {this.dockingContent} - - <div className="mainView-propertiesDragger" key="props" onPointerDown={this.onPropertiesPointerDown} style={{ right: this.propertiesWidth() - 1 }}> - <FontAwesomeIcon icon={this.propertiesWidth() < 10 ? "chevron-left" : "chevron-right"} color={this.colorScheme === ColorScheme.Dark ? Colors.WHITE : Colors.BLACK} size="sm" /> + return ( + <> + {this.leftMenuPanel} + <div key="inner" className={`mainView-innerContent${this.colorScheme}`}> + {this.flyout} + <div className="mainView-libraryHandle" style={{ left: leftMenuFlyoutWidth - 10 /* ~half width of handle */, display: !this._leftMenuFlyoutWidth ? 'none' : undefined }} onPointerDown={this.onFlyoutPointerDown}> + <FontAwesomeIcon icon="chevron-left" color={this.colorScheme === ColorScheme.Dark ? 'white' : 'black'} style={{ opacity: '50%' }} size="sm" /> </div> - <div className="properties-container" style={{ width: this.propertiesWidth() }}> - {this.propertiesWidth() < 10 ? (null) : <PropertiesView styleProvider={DefaultStyleProvider} addDocTab={this.addDocTabFunc} width={this.propertiesWidth()} height={this.propertiesHeight()} />} + <div className="mainView-innerContainer" style={{ width: `calc(100% - ${width}px)` }}> + {this.dockingContent} + + <div className="mainView-propertiesDragger" key="props" onPointerDown={this.onPropertiesPointerDown} style={{ right: this.propertiesWidth() - 1 }}> + <FontAwesomeIcon icon={this.propertiesWidth() < 10 ? 'chevron-left' : 'chevron-right'} color={this.colorScheme === ColorScheme.Dark ? Colors.WHITE : Colors.BLACK} size="sm" /> + </div> + <div className="properties-container" style={{ width: this.propertiesWidth() }}> + {this.propertiesWidth() < 10 ? null : <PropertiesView styleProvider={DefaultStyleProvider} addDocTab={this.addDocTabFunc} width={this.propertiesWidth()} height={this.propertiesHeight()} />} + </div> </div> </div> - </div> - </>; + </> + ); } - @computed get mainDashboardArea() { - return !this.userDoc ? (null) : - <div className="mainView-dashboardArea" ref={r => { - r && new _global.ResizeObserver(action(() => { - this._dashUIWidth = r.getBoundingClientRect().width; - this._dashUIHeight = r.getBoundingClientRect().height; - })).observe(r); - }} style={{ - color: this.colorScheme === ColorScheme.Dark ? "rgb(205,205,205)" : "black", - height: `calc(100% - ${this.topOfDashUI + this.topMenuHeight()}px)`, - width: "100%", - }} > + return !this.userDoc ? null : ( + <div + className="mainView-dashboardArea" + ref={r => { + r && + new _global.ResizeObserver( + action(() => { + this._dashUIWidth = r.getBoundingClientRect().width; + this._dashUIHeight = r.getBoundingClientRect().height; + }) + ).observe(r); + }} + style={{ + color: this.colorScheme === ColorScheme.Dark ? 'rgb(205,205,205)' : 'black', + height: `calc(100% - ${this.topOfDashUI + this.topMenuHeight()}px)`, + width: '100%', + }}> {this.mainInnerContent} - </div>; + </div> + ); } - expandFlyout = action((button: Doc) => { - // bcz: What's going on here!? + // bcz: What's going on here!? // Chrome(not firefox) seems to have a bug when the flyout expands and there's a zoomed freeform tab. All of the div below the CollectionFreeFormView's main div // generate the wrong value from getClientRectangle() -- specifically they return an 'x' that is the flyout's width greater than it should be. // interactively adjusting the flyout fixes the problem. So does programmatically changing the value after a timeout to something *fractionally* different (ie, 1.5, not 1);) - this._leftMenuFlyoutWidth = (this._leftMenuFlyoutWidth || 250); - setTimeout(action(() => this._leftMenuFlyoutWidth += 0.5), 0); + this._leftMenuFlyoutWidth = this._leftMenuFlyoutWidth || 250; + setTimeout( + action(() => (this._leftMenuFlyoutWidth += 0.5)), + 0 + ); this._sidebarContent.proto = button.target as any; this.LastButton = button; @@ -502,29 +810,29 @@ export class MainView extends React.Component { closeFlyout = action(() => { this.LastButton = undefined; - this._panelContent = "none"; + this._panelContent = 'none'; this._sidebarContent.proto = undefined; this._leftMenuFlyoutWidth = 0; }); - remButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.RemoveDocFromList(CurrentUserUtils.MyDockedBtns, "data", doc), true); + remButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.RemoveDocFromList(CurrentUserUtils.MyDockedBtns, 'data', doc), true); moveButtonDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => this.remButtonDoc(doc) && addDocument(doc); - addButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.AddDocToList(CurrentUserUtils.MyDockedBtns, "data", doc), true); + addButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.AddDocToList(CurrentUserUtils.MyDockedBtns, 'data', doc), true); buttonBarXf = () => { if (!this._docBtnRef.current) return Transform.Identity(); const { scale, translateX, translateY } = Utils.GetScreenTransform(this._docBtnRef.current); return new Transform(-translateX, -translateY, 1 / scale); - } + }; @computed get docButtons() { - return !CurrentUserUtils.MyDockedBtns ? (null) : - <div className="mainView-docButtons" ref={this._docBtnRef} style={{ height: !CurrentUserUtils.MyDockedBtns.linearViewIsExpanded ? "42px" : undefined }} > + return !CurrentUserUtils.MyDockedBtns ? null : ( + <div className="mainView-docButtons" ref={this._docBtnRef} style={{ height: !CurrentUserUtils.MyDockedBtns.linearViewIsExpanded ? '42px' : undefined }}> <CollectionLinearView Document={CurrentUserUtils.MyDockedBtns} DataDoc={undefined} - fieldKey={"data"} - dropAction={"alias"} + fieldKey={'data'} + dropAction={'alias'} setHeight={returnFalse} styleProvider={DefaultStyleProvider} rootSelected={returnTrue} @@ -550,51 +858,62 @@ export class MainView extends React.Component { docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} /> - {['watching', 'recording'].includes(String(this.userDoc?.presentationMode) ?? '') ? <div style={{ border: '.5rem solid green', padding: '5px' }}>{this.userDoc?.presentationMode}</div> : <></>} - </div>; + ContainingCollectionDoc={undefined} + /> + {['watching', 'recording'].includes(String(this.userDoc?.presentationMode) ?? '') ? <div style={{ border: '.5rem solid green', padding: '5px' }}>{StrCast(this.userDoc?.presentationMode)}</div> : <></>} + </div> + ); } @computed get snapLines() { - return !SnappingManager.GetShowSnapLines() ? (null) : <div className="mainView-snapLines"> - <svg style={{ width: "100%", height: "100%" }}> - {SnappingManager.horizSnapLines().map(l => <line x1="0" y1={l} x2="2000" y2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={"1 1"} />)} - {SnappingManager.vertSnapLines().map(l => <line y1="0" x1={l} y2="2000" x2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={"1 1"} />)} - </svg> - </div>; + return !SnappingManager.GetShowSnapLines() ? null : ( + <div className="mainView-snapLines"> + <svg style={{ width: '100%', height: '100%' }}> + {SnappingManager.horizSnapLines().map(l => ( + <line x1="0" y1={l} x2="2000" y2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={'1 1'} /> + ))} + {SnappingManager.vertSnapLines().map(l => ( + <line y1="0" x1={l} y2="2000" x2={l} stroke="black" opacity={0.3} strokeWidth={0.5} strokeDasharray={'1 1'} /> + ))} + </svg> + </div> + ); } @computed get inkResources() { - return <svg width={0} height={0}> - <defs> - <filter id="inkSelectionHalo"> - <feColorMatrix type="matrix" - result="color" - values="1 0 0 0 0 + return ( + <svg width={0} height={0}> + <defs> + <filter id="inkSelectionHalo"> + <feColorMatrix + type="matrix" + result="color" + values="1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 1 0"> - </feColorMatrix> - <feGaussianBlur in="color" stdDeviation="4" result="blur"></feGaussianBlur> - <feOffset in="blur" dx="0" dy="0" result="offset"></feOffset> - <feMerge> - <feMergeNode in="bg"></feMergeNode> - <feMergeNode in="offset"></feMergeNode> - <feMergeNode in="SourceGraphic"></feMergeNode> - </feMerge> - </filter> - </defs> - </svg>; + 0 0 0 1 0"></feColorMatrix> + <feGaussianBlur in="color" stdDeviation="4" result="blur"></feGaussianBlur> + <feOffset in="blur" dx="0" dy="0" result="offset"></feOffset> + <feMerge> + <feMergeNode in="bg"></feMergeNode> + <feMergeNode in="offset"></feMergeNode> + <feMergeNode in="SourceGraphic"></feMergeNode> + </feMerge> + </filter> + </defs> + </svg> + ); } - @computed get invisibleWebBox() { // see note under the makeLink method in HypothesisUtils.ts - return !DocumentLinksButton.invisibleWebDoc ? null : + @computed get invisibleWebBox() { + // see note under the makeLink method in HypothesisUtils.ts + return !DocumentLinksButton.invisibleWebDoc ? null : ( <div className="mainView-invisibleWebRef" ref={DocumentLinksButton.invisibleWebRef}> <WebBox - fieldKey={"data"} + fieldKey={'data'} ContainingCollectionView={undefined} ContainingCollectionDoc={undefined} Document={DocumentLinksButton.invisibleWebDoc} - dropAction={"move"} + dropAction={'move'} styleProvider={undefined} isSelected={returnFalse} select={returnFalse} @@ -615,108 +934,131 @@ export class MainView extends React.Component { docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} /> - </div>; + </div> + ); } render() { - return (<div className={`mainView-container${this.colorScheme}`} - onScroll={() => ((ele) => ele.scrollTop = ele.scrollLeft = 0)(document.getElementById("root")!)} - ref={r => { - r && new _global.ResizeObserver(action(() => { this._windowWidth = r.getBoundingClientRect().width; this._windowHeight = r.getBoundingClientRect().height; })).observe(r); - }}> - {this.inkResources} - <DictationOverlay /> - <SharingManager /> - <SettingsManager /> - <CaptureManager /> - <GroupManager /> - <GoogleAuthenticationManager /> - <DocumentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfHeaderBarDoc} PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} /> - <ComponentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfMainDocContent} /> - <TopBar /> - {LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null} - {DocumentLinksButton.LinkEditorDocView ? <LinkMenu clearLinkEditor={action(() => DocumentLinksButton.LinkEditorDocView = undefined)} docView={DocumentLinksButton.LinkEditorDocView} /> : (null)} - {LinkDocPreview.LinkInfo ? <LinkDocPreview {...LinkDocPreview.LinkInfo} /> : (null)} - - {((page:string) => { - switch (page) { - case "dashboard": - default:return <> - <div style={{ position: "relative", display: LightboxView.LightboxDoc ? "none" : undefined, zIndex: 1999 }} > - <CollectionMenu panelWidth={this.topMenuWidth} panelHeight={this.topMenuHeight} /> - </div> - <GestureOverlay> {this.mainDashboardArea} </GestureOverlay> - </>; - case "home": return <DashboardView/>; - } })(CurrentUserUtils.ActivePage) - } - - <PreviewCursor /> - <TaskCompletionBox /> - <ContextMenu /> - <RadialMenu /> - <AnchorMenu /> - <DashFieldViewMenu /> - <MarqueeOptionsMenu /> - <OverlayView /> - <TimelineMenu /> - <RichTextMenu /> - <InkTranscription /> - {this.snapLines} - <div className="mainView-webRef" ref={this.makeWebRef} /> - <LightboxView PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} /> - </div >); + return ( + <div + className={`mainView-container${this.colorScheme}`} + onScroll={() => (ele => (ele.scrollTop = ele.scrollLeft = 0))(document.getElementById('root')!)} + ref={r => { + r && + new _global.ResizeObserver( + action(() => { + this._windowWidth = r.getBoundingClientRect().width; + this._windowHeight = r.getBoundingClientRect().height; + }) + ).observe(r); + }}> + {this.inkResources} + <DictationOverlay /> + <SharingManager /> + <SettingsManager /> + <CaptureManager /> + <GroupManager /> + <GoogleAuthenticationManager /> + <DocumentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfHeaderBarDoc} PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} /> + <ComponentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfMainDocContent} /> + <TopBar /> + {LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null} + {DocumentLinksButton.LinkEditorDocView ? <LinkMenu clearLinkEditor={action(() => (DocumentLinksButton.LinkEditorDocView = undefined))} docView={DocumentLinksButton.LinkEditorDocView} /> : null} + {LinkDocPreview.LinkInfo ? <LinkDocPreview {...LinkDocPreview.LinkInfo} /> : null} + + {((page: string) => { + switch (page) { + case 'dashboard': + default: + return ( + <> + <div style={{ position: 'relative', display: LightboxView.LightboxDoc ? 'none' : undefined, zIndex: 1999 }}> + <CollectionMenu panelWidth={this.topMenuWidth} panelHeight={this.topMenuHeight} /> + </div> + <GestureOverlay> {this.mainDashboardArea} </GestureOverlay> + </> + ); + case 'home': + return <DashboardView />; + } + })(CurrentUserUtils.ActivePage)} + + <PreviewCursor /> + <TaskCompletionBox /> + <ContextMenu /> + <RadialMenu /> + <AnchorMenu /> + <DashFieldViewMenu /> + <MarqueeOptionsMenu /> + <OverlayView /> + <TimelineMenu /> + <RichTextMenu /> + <InkTranscription /> + {this.snapLines} + <div className="mainView-webRef" ref={this.makeWebRef} /> + <LightboxView PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} /> + </div> + ); } makeWebRef = (ele: HTMLDivElement) => { - reaction(() => DocumentLinksButton.invisibleWebDoc, + reaction( + () => DocumentLinksButton.invisibleWebDoc, invisibleDoc => { ReactDOM.unmountComponentAtNode(ele); - invisibleDoc && ReactDOM.render(<span title="Drag as document" className="invisible-webbox" > - <div className="mainView-webRef" ref={DocumentLinksButton.invisibleWebRef}> - <WebBox - fieldKey={"data"} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} - Document={invisibleDoc} - dropAction={"move"} - isSelected={returnFalse} - docViewPath={returnEmptyDoclist} - select={returnFalse} - rootSelected={returnFalse} - renderDepth={0} - setHeight={returnFalse} - styleProvider={undefined} - addDocTab={returnFalse} - pinToPres={returnFalse} - ScreenToLocalTransform={Transform.Identity} - bringToFront={returnFalse} - isContentActive={emptyFunction} - whenChildContentsActiveChanged={returnFalse} - focus={returnFalse} - PanelWidth={() => 500} - PanelHeight={() => 800} - docFilters={returnEmptyFilter} - docRangeFilters={returnEmptyFilter} - searchFilterDocs={returnEmptyDoclist} - /> - </div>; - </span>, ele); + invisibleDoc && + ReactDOM.render( + <span title="Drag as document" className="invisible-webbox"> + <div className="mainView-webRef" ref={DocumentLinksButton.invisibleWebRef}> + <WebBox + fieldKey={'data'} + ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} + Document={invisibleDoc} + dropAction={'move'} + isSelected={returnFalse} + docViewPath={returnEmptyDoclist} + select={returnFalse} + rootSelected={returnFalse} + renderDepth={0} + setHeight={returnFalse} + styleProvider={undefined} + addDocTab={returnFalse} + pinToPres={returnFalse} + ScreenToLocalTransform={Transform.Identity} + bringToFront={returnFalse} + isContentActive={emptyFunction} + whenChildContentsActiveChanged={returnFalse} + focus={returnFalse} + PanelWidth={() => 500} + PanelHeight={() => 800} + docFilters={returnEmptyFilter} + docRangeFilters={returnEmptyFilter} + searchFilterDocs={returnEmptyDoclist} + /> + </div> + ; + </span>, + ele + ); let success = false; const onSuccess = () => { success = true; clearTimeout(interval); - document.removeEventListener("editSuccess", onSuccess); + document.removeEventListener('editSuccess', onSuccess); }; // For some reason, Hypothes.is annotations don't load until a click is registered on the page, // so we keep simulating clicks until annotations have loaded and editing is successful const interval = setInterval(() => !success && simulateMouseClick(ele, 50, 50, 50, 50), 500); setTimeout(() => !success && clearInterval(interval), 10000); // give up if no success after 10s - document.addEventListener("editSuccess", onSuccess); - }); - } + document.addEventListener('editSuccess', onSuccess); + } + ); + }; } -ScriptingGlobals.add(function selectMainMenu(doc: Doc, title: string) { MainView.Instance.selectMenu(doc); });
\ No newline at end of file +ScriptingGlobals.add(function selectMainMenu(doc: Doc, title: string) { + MainView.Instance.selectMenu(doc); +}); diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx index 789756a78..436cb688f 100644 --- a/src/client/views/Touchable.tsx +++ b/src/client/views/Touchable.tsx @@ -4,7 +4,7 @@ import { InteractionUtils } from '../util/InteractionUtils'; const HOLD_DURATION = 1000; -export abstract class Touchable<T = {}> extends React.Component<T> { +export abstract class Touchable<T = {}> extends React.Component<React.PropsWithChildren<T>> { //private holdTimer: NodeJS.Timeout | undefined; private moveDisposer?: InteractionUtils.MultiTouchEventDisposer; private endDisposer?: InteractionUtils.MultiTouchEventDisposer; @@ -25,7 +25,6 @@ export abstract class Touchable<T = {}> extends React.Component<T> { */ @action protected onTouchStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): void => { - const actualPts: React.Touch[] = []; const te = me.touchEvent; // loop through all touches on screen @@ -60,7 +59,7 @@ export abstract class Touchable<T = {}> extends React.Component<T> { case 1: this.handle1PointerDown(te, me); te.persist(); - // -- code for radial menu -- + // -- code for radial menu -- // if (this.holdTimer) { // clearTimeout(this.holdTimer) // this.holdTimer = undefined; @@ -71,11 +70,11 @@ export abstract class Touchable<T = {}> extends React.Component<T> { break; } } - } + }; /** - * Handle touch move event - */ + * Handle touch move event + */ @action protected onTouch = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => { const te = me.touchEvent; @@ -85,8 +84,12 @@ export abstract class Touchable<T = {}> extends React.Component<T> { if (!InteractionUtils.IsDragging(this.prevPoints, myTouches, 5) && !this._touchDrag) return; this._touchDrag = true; switch (myTouches.length) { - case 1: this.handle1PointerMove(te, me); break; - case 2: this.handle2PointersMove(te, me); break; + case 1: + this.handle1PointerMove(te, me); + break; + case 2: + this.handle2PointersMove(te, me); + break; } for (const pt of me.touches) { @@ -94,7 +97,7 @@ export abstract class Touchable<T = {}> extends React.Component<T> { this.prevPoints.set(pt.identifier, pt); } } - } + }; @action protected onTouchEnd = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => { @@ -110,7 +113,6 @@ export abstract class Touchable<T = {}> extends React.Component<T> { this._touchDrag = false; te.stopPropagation(); - // if (e.targetTouches.length === 0) { // this.prevPoints.clear(); // } @@ -119,36 +121,36 @@ export abstract class Touchable<T = {}> extends React.Component<T> { this.cleanUpInteractions(); } e.stopPropagation(); - } + }; cleanUpInteractions = (): void => { this.removeMoveListeners(); this.removeEndListeners(); - } + }; handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>): any => { e.stopPropagation(); e.preventDefault(); - } + }; handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>): any => { e.stopPropagation(); e.preventDefault(); - } + }; handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => { this.removeMoveListeners(); this.addMoveListeners(); this.removeEndListeners(); this.addEndListeners(); - } + }; handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => { this.removeMoveListeners(); this.addMoveListeners(); this.removeEndListeners(); this.addEndListeners(); - } + }; handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => { e.stopPropagation(); @@ -159,30 +161,30 @@ export abstract class Touchable<T = {}> extends React.Component<T> { this.removeHoldEndListeners(); this.addHoldMoveListeners(); this.addHoldEndListeners(); - } + }; addMoveListeners = () => { const handler = (e: Event) => this.onTouch(e, (e as CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>).detail); - document.addEventListener("dashOnTouchMove", handler); - this.moveDisposer = () => document.removeEventListener("dashOnTouchMove", handler); - } + document.addEventListener('dashOnTouchMove', handler); + this.moveDisposer = () => document.removeEventListener('dashOnTouchMove', handler); + }; addEndListeners = () => { const handler = (e: Event) => this.onTouchEnd(e, (e as CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>).detail); - document.addEventListener("dashOnTouchEnd", handler); - this.endDisposer = () => document.removeEventListener("dashOnTouchEnd", handler); - } + document.addEventListener('dashOnTouchEnd', handler); + this.endDisposer = () => document.removeEventListener('dashOnTouchEnd', handler); + }; addHoldMoveListeners = () => { const handler = (e: Event) => this.handle1PointerHoldMove(e, (e as CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>).detail); - document.addEventListener("dashOnTouchHoldMove", handler); - this.holdMoveDisposer = () => document.removeEventListener("dashOnTouchHoldMove", handler); - } + document.addEventListener('dashOnTouchHoldMove', handler); + this.holdMoveDisposer = () => document.removeEventListener('dashOnTouchHoldMove', handler); + }; addHoldEndListeners = () => { const handler = (e: Event) => this.handle1PointerHoldEnd(e, (e as CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>).detail); - document.addEventListener("dashOnTouchHoldEnd", handler); - this.holdEndDisposer = () => document.removeEventListener("dashOnTouchHoldEnd", handler); - } + document.addEventListener('dashOnTouchHoldEnd', handler); + this.holdEndDisposer = () => document.removeEventListener('dashOnTouchHoldEnd', handler); + }; removeMoveListeners = () => this.moveDisposer?.(); removeEndListeners = () => this.endDisposer?.(); @@ -192,7 +194,7 @@ export abstract class Touchable<T = {}> extends React.Component<T> { handle1PointerHoldMove = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => { // e.stopPropagation(); // me.touchEvent.stopPropagation(); - } + }; handle1PointerHoldEnd = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => { e.stopPropagation(); @@ -202,10 +204,10 @@ export abstract class Touchable<T = {}> extends React.Component<T> { me.touchEvent.stopPropagation(); me.touchEvent.preventDefault(); - } + }; handleHandDown = (e: React.TouchEvent) => { // e.stopPropagation(); // e.preventDefault(); - } -}
\ No newline at end of file + }; +} diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx index e80ba6f36..7a393a4f7 100644 --- a/src/client/views/animationtimeline/Timeline.tsx +++ b/src/client/views/animationtimeline/Timeline.tsx @@ -1,19 +1,19 @@ -import { faBackward, faForward, faGripLines, faPauseCircle, faPlayCircle } from "@fortawesome/free-solid-svg-icons"; +import { faBackward, faForward, faGripLines, faPauseCircle, faPlayCircle } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, observable, runInAction, trace } from "mobx"; -import { observer } from "mobx-react"; -import * as React from "react"; -import { Doc, DocListCast } from "../../../fields/Doc"; -import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types"; -import { Utils, setupMoveUpEvents, emptyFunction, returnFalse } from "../../../Utils"; -import { FieldViewProps } from "../nodes/FieldView"; -import { KeyframeFunc } from "./Keyframe"; -import "./Timeline.scss"; -import { TimelineOverview } from "./TimelineOverview"; -import { Track } from "./Track"; -import clamp from "../../util/clamp"; -import { DocumentType } from "../../documents/DocumentTypes"; -import { IconLookup } from "@fortawesome/fontawesome-svg-core"; +import { action, computed, observable, runInAction, trace } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { Doc, DocListCast } from '../../../fields/Doc'; +import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types'; +import { Utils, setupMoveUpEvents, emptyFunction, returnFalse } from '../../../Utils'; +import { FieldViewProps } from '../nodes/FieldView'; +import { KeyframeFunc } from './Keyframe'; +import './Timeline.scss'; +import { TimelineOverview } from './TimelineOverview'; +import { Track } from './Track'; +import clamp from '../../util/clamp'; +import { DocumentType } from '../../documents/DocumentTypes'; +import { IconLookup } from '@fortawesome/fontawesome-svg-core'; /** * Timeline class controls most of timeline functions besides individual keyframe and track mechanism. Main functions are @@ -35,10 +35,8 @@ import { IconLookup } from "@fortawesome/fontawesome-svg-core"; @author Andrew Kim */ - @observer export class Timeline extends React.Component<FieldViewProps> { - //readonly constants private readonly DEFAULT_TICK_SPACING: number = 50; private readonly MAX_TITLE_HEIGHT = 75; @@ -57,8 +55,7 @@ export class Timeline extends React.Component<FieldViewProps> { @observable private _roundToggleRef = React.createRef<HTMLDivElement>(); @observable private _roundToggleContainerRef = React.createRef<HTMLDivElement>(); - - //boolean vars and instance vars + //boolean vars and instance vars @observable private _currentBarX: number = 0; @observable private _windSpeed: number = 1; @observable private _isPlaying: boolean = false; //scrubber playing @@ -73,13 +70,13 @@ export class Timeline extends React.Component<FieldViewProps> { @observable private _titleHeight = 0; /** - * collection get method. Basically defines what defines collection's children. These will be tracked in the timeline. Do not edit. + * collection get method. Basically defines what defines collection's children. These will be tracked in the timeline. Do not edit. */ @computed private get children(): Doc[] { const annotatedDoc = [DocumentType.IMG, DocumentType.VID, DocumentType.PDF, DocumentType.MAP].includes(StrCast(this.props.Document.type) as any); if (annotatedDoc) { - return DocListCast(this.props.Document[Doc.LayoutFieldKey(this.props.Document) + "-annotations"]); + return DocListCast(this.props.Document[Doc.LayoutFieldKey(this.props.Document) + '-annotations']); } return DocListCast(this.props.Document[this.props.fieldKey]); } @@ -91,7 +88,8 @@ export class Timeline extends React.Component<FieldViewProps> { this._titleHeight = relativeHeight < this.MAX_TITLE_HEIGHT ? relativeHeight : this.MAX_TITLE_HEIGHT; //check if relHeight is less than Maxheight. Else, just set relheight to max this.MIN_CONTAINER_HEIGHT = this._titleHeight + 130; //offset this.DEFAULT_CONTAINER_HEIGHT = this._titleHeight * 2 + 130; //twice the titleheight + offset - if (!this.props.Document.AnimationLength) { //if animation length did not exist + if (!this.props.Document.AnimationLength) { + //if animation length did not exist this.props.Document.AnimationLength = this._time; //set it to default time } else { this._time = NumCast(this.props.Document.AnimationLength); //else, set time to animationlength stored from before @@ -116,24 +114,29 @@ export class Timeline extends React.Component<FieldViewProps> { drawTicks = () => { const ticks = []; for (let i = 0; i < this._time / this._tickIncrement; i++) { - ticks.push(<div key={Utils.GenerateGuid()} className="tick" style={{ transform: `translate(${i * this._tickSpacing}px)`, position: "absolute", pointerEvents: "none" }}> <p className="number-label">{this.toReadTime(i * this._tickIncrement)}</p></div>); + ticks.push( + <div key={Utils.GenerateGuid()} className="tick" style={{ transform: `translate(${i * this._tickSpacing}px)`, position: 'absolute', pointerEvents: 'none' }}> + {' '} + <p className="number-label">{this.toReadTime(i * this._tickIncrement)}</p> + </div> + ); } return ticks; - } + }; /** * changes the scrubber to actual pixel position */ @action changeCurrentBarX = (pixel: number) => { - pixel <= 0 ? this._currentBarX = 0 : pixel >= this._totalLength ? this._currentBarX = this._totalLength : this._currentBarX = pixel; - } + pixel <= 0 ? (this._currentBarX = 0) : pixel >= this._totalLength ? (this._currentBarX = this._totalLength) : (this._currentBarX = pixel); + }; //for playing onPlay = (e: React.MouseEvent) => { e.stopPropagation(); this.play(); - } + }; /** * when playbutton is clicked @@ -149,8 +152,7 @@ export class Timeline extends React.Component<FieldViewProps> { this._isPlaying = !this._isPlaying; this._playButton = this._isPlaying ? faPauseCircle : faPlayCircle; this._isPlaying && playTimeline(); - } - + }; /** * fast forward the timeline scrubbing @@ -159,30 +161,32 @@ export class Timeline extends React.Component<FieldViewProps> { windForward = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); - if (this._windSpeed < 64) { //max speed is 32 + if (this._windSpeed < 64) { + //max speed is 32 this._windSpeed = this._windSpeed * 2; } - } + }; /** - * rewind the timeline scrubbing + * rewind the timeline scrubbing */ @action windBackward = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); - if (this._windSpeed > 1 / 16) { // min speed is 1/8 + if (this._windSpeed > 1 / 16) { + // min speed is 1/8 this._windSpeed = this._windSpeed / 2; } - } + }; /** - * scrubber down + * scrubber down */ @action onScrubberDown = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, this.onScrubberMove, emptyFunction, emptyFunction); - } + }; /** * when there is any scrubber movement @@ -194,16 +198,15 @@ export class Timeline extends React.Component<FieldViewProps> { const offsetX = Math.round(e.clientX - left) * this.props.ScreenToLocalTransform().Scale; this.changeCurrentBarX(offsetX + this._visibleStart); //changes scrubber to clicked scrubber position return false; - } + }; /** * when panning the timeline (in editing mode) */ @action onPanDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, this.onPanMove, emptyFunction, (e) => - this.changeCurrentBarX(this._trackbox.current!.scrollLeft + e.clientX - this._trackbox.current!.getBoundingClientRect().left)); - } + setupMoveUpEvents(this, e, this.onPanMove, emptyFunction, e => this.changeCurrentBarX(this._trackbox.current!.scrollLeft + e.clientX - this._trackbox.current!.getBoundingClientRect().left)); + }; /** * when moving the timeline (in editing mode) @@ -218,29 +221,34 @@ export class Timeline extends React.Component<FieldViewProps> { if (this._visibleStart + this._visibleLength + 20 >= this._totalLength) { this._visibleStart -= e.movementX; this._totalLength -= e.movementX; - this._time -= KeyframeFunc.convertPixelTime(e.movementX, "mili", "time", this._tickSpacing, this._tickIncrement); + this._time -= KeyframeFunc.convertPixelTime(e.movementX, 'mili', 'time', this._tickSpacing, this._tickIncrement); this.props.Document.AnimationLength = this._time; } return false; - } - + }; @action movePanX = (pixel: number) => { this._infoContainer.current!.scrollLeft = pixel; this._visibleStart = this._infoContainer.current!.scrollLeft; - } + }; /** * resizing timeline (in editing mode) (the hamburger drag icon) */ onResizeDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, action((e) => { - const offset = e.clientY - this._timelineContainer.current!.getBoundingClientRect().bottom; - this._containerHeight = clamp(this.MIN_CONTAINER_HEIGHT, this._containerHeight + offset, this.MAX_CONTAINER_HEIGHT); - return false; - }), emptyFunction, emptyFunction); - } + setupMoveUpEvents( + this, + e, + action(e => { + const offset = e.clientY - this._timelineContainer.current!.getBoundingClientRect().bottom; + this._containerHeight = clamp(this.MIN_CONTAINER_HEIGHT, this._containerHeight + offset, this.MAX_CONTAINER_HEIGHT); + return false; + }), + emptyFunction, + emptyFunction + ); + }; /** * for displaying time to standard min:sec @@ -251,19 +259,18 @@ export class Timeline extends React.Component<FieldViewProps> { const inSeconds = Math.round(time * 100) / 100; const min = Math.floor(inSeconds / 60); - const sec = (Math.round((inSeconds % 60) * 100) / 100); + const sec = Math.round((inSeconds % 60) * 100) / 100; let secString = sec.toFixed(2); if (Math.floor(sec / 10) === 0) { - secString = "0" + secString; + secString = '0' + secString; } return `${min}:${secString}`; - } - + }; /** - * timeline zoom function + * timeline zoom function * use mouse middle button to zoom in/out the timeline */ @action @@ -271,17 +278,16 @@ export class Timeline extends React.Component<FieldViewProps> { e.preventDefault(); e.stopPropagation(); const offset = e.clientX - this._infoContainer.current!.getBoundingClientRect().left; - const prevTime = KeyframeFunc.convertPixelTime(this._visibleStart + offset, "mili", "time", this._tickSpacing, this._tickIncrement); - const prevCurrent = KeyframeFunc.convertPixelTime(this._currentBarX, "mili", "time", this._tickSpacing, this._tickIncrement); + const prevTime = KeyframeFunc.convertPixelTime(this._visibleStart + offset, 'mili', 'time', this._tickSpacing, this._tickIncrement); + const prevCurrent = KeyframeFunc.convertPixelTime(this._currentBarX, 'mili', 'time', this._tickSpacing, this._tickIncrement); this.zoom(e.deltaY < 0); - const currPixel = KeyframeFunc.convertPixelTime(prevTime, "mili", "pixel", this._tickSpacing, this._tickIncrement); - const currCurrent = KeyframeFunc.convertPixelTime(prevCurrent, "mili", "pixel", this._tickSpacing, this._tickIncrement); + const currPixel = KeyframeFunc.convertPixelTime(prevTime, 'mili', 'pixel', this._tickSpacing, this._tickIncrement); + const currCurrent = KeyframeFunc.convertPixelTime(prevCurrent, 'mili', 'pixel', this._tickSpacing, this._tickIncrement); this._infoContainer.current!.scrollLeft = currPixel - offset; this._visibleStart = currPixel - offset > 0 ? currPixel - offset : 0; this._visibleStart += this._visibleLength + this._visibleStart > this._totalLength ? this._totalLength - (this._visibleStart + this._visibleLength) : 0; this.changeCurrentBarX(currCurrent); - } - + }; resetView(doc: Doc) { doc._panX = doc._customOriginX ?? 0; @@ -324,7 +330,7 @@ export class Timeline extends React.Component<FieldViewProps> { this._tickSpacing = spacingChange; this._tickIncrement = incrementChange; } - } + }; /** * tool box includes the toggle buttons at the top of the timeline (both editing mode and play mode) @@ -333,60 +339,90 @@ export class Timeline extends React.Component<FieldViewProps> { const size = 40 * scale; //50 is default const iconSize = 25; const width: number = this.props.PanelWidth(); - const modeType = this.props.Document.isATOn ? "Author" : "Play"; + const modeType = this.props.Document.isATOn ? 'Author' : 'Play'; //decides if information should be omitted because the timeline is very small // if its less than 950 pixels then it's going to be overlapping - let modeString = modeType, overviewString = "", lengthString = ""; + let modeString = modeType, + overviewString = '', + lengthString = ''; if (width < 850) { - modeString = "Mode: " + modeType; - overviewString = "Overview:"; - lengthString = "Length: "; + modeString = 'Mode: ' + modeType; + overviewString = 'Overview:'; + lengthString = 'Length: '; } return ( <div key="timeline_toolbox" className="timeline-toolbox" style={{ height: `${size}px` }}> <div className="playbackControls"> - <div className="timeline-icon" key="timeline_windBack" onClick={this.windBackward} title="Slow Down Animation"> <FontAwesomeIcon icon={faBackward as IconLookup} style={{ height: `${iconSize}px`, width: `${iconSize}px` }} /> </div> - <div className="timeline-icon" key=" timeline_play" onClick={this.onPlay} title="Play/Pause"> <FontAwesomeIcon icon={this._playButton as IconLookup} style={{ height: `${iconSize}px`, width: `${iconSize}px` }} /> </div> - <div className="timeline-icon" key="timeline_windForward" onClick={this.windForward} title="Speed Up Animation"> <FontAwesomeIcon icon={faForward as IconLookup} style={{ height: `${iconSize}px`, width: `${iconSize}px` }} /> </div> + <div className="timeline-icon" key="timeline_windBack" onClick={this.windBackward} title="Slow Down Animation"> + {' '} + <FontAwesomeIcon icon={faBackward as IconLookup} style={{ height: `${iconSize}px`, width: `${iconSize}px` }} />{' '} + </div> + <div className="timeline-icon" key=" timeline_play" onClick={this.onPlay} title="Play/Pause"> + {' '} + <FontAwesomeIcon icon={this._playButton as IconLookup} style={{ height: `${iconSize}px`, width: `${iconSize}px` }} />{' '} + </div> + <div className="timeline-icon" key="timeline_windForward" onClick={this.windForward} title="Speed Up Animation"> + {' '} + <FontAwesomeIcon icon={faForward as IconLookup} style={{ height: `${iconSize}px`, width: `${iconSize}px` }} />{' '} + </div> </div> <div className="grid-box overview-tool"> <div className="overview-box"> - <div key="overview-text" className="animation-text">{overviewString}</div> - <TimelineOverview tickSpacing={this._tickSpacing} tickIncrement={this._tickIncrement} time={this._time} parent={this} isAuthoring={BoolCast(this.props.Document.isATOn)} currentBarX={this._currentBarX} totalLength={this._totalLength} visibleLength={this._visibleLength} visibleStart={this._visibleStart} changeCurrentBarX={this.changeCurrentBarX} movePanX={this.movePanX} /> + <div key="overview-text" className="animation-text"> + {overviewString} + </div> + <TimelineOverview + tickSpacing={this._tickSpacing} + tickIncrement={this._tickIncrement} + time={this._time} + parent={this} + isAuthoring={BoolCast(this.props.Document.isATOn)} + currentBarX={this._currentBarX} + totalLength={this._totalLength} + visibleLength={this._visibleLength} + visibleStart={this._visibleStart} + changeCurrentBarX={this.changeCurrentBarX} + movePanX={this.movePanX} + /> </div> <div className="mode-box overview-tool"> - <div key="animation-text" className="animation-text">{modeString}</div> + <div key="animation-text" className="animation-text"> + {modeString} + </div> <div key="round-toggle" ref={this._roundToggleContainerRef} className="round-toggle"> - <div key="round-toggle-slider" ref={this._roundToggleRef} className="round-toggle-slider" onPointerDown={this.toggleChecked}> </div> + <div key="round-toggle-slider" ref={this._roundToggleRef} className="round-toggle-slider" onPointerDown={this.toggleChecked}> + {' '} + </div> </div> </div> - <div className="time-box overview-tool" style={{ display: "flex" }}> + <div className="time-box overview-tool" style={{ display: 'flex' }}> {this.timeIndicator(lengthString, totalTime)} - <div className="resetView-tool" title="Return to Default View" onClick={() => this.resetView(this.props.Document)}><FontAwesomeIcon icon="compress-arrows-alt" size="lg" /></div> - <div className="resetView-tool" style={{ display: this.props.Document.isATOn ? "flex" : "none" }} title="Set Default View" onClick={() => this.setView(this.props.Document)}><FontAwesomeIcon icon="expand-arrows-alt" size="lg" /></div> + <div className="resetView-tool" title="Return to Default View" onClick={() => this.resetView(this.props.Document)}> + <FontAwesomeIcon icon="compress-arrows-alt" size="lg" /> + </div> + <div className="resetView-tool" style={{ display: this.props.Document.isATOn ? 'flex' : 'none' }} title="Set Default View" onClick={() => this.setView(this.props.Document)}> + <FontAwesomeIcon icon="expand-arrows-alt" size="lg" /> + </div> </div> </div> </div> ); - } + }; timeIndicator(lengthString: string, totalTime: number) { if (this.props.Document.isATOn) { - return ( - <div key="time-text" className="animation-text" style={{ visibility: this.props.Document.isATOn ? "visible" : "hidden", display: this.props.Document.isATOn ? "flex" : "none" }}>{`Total: ${this.toReadTime(totalTime)}`}</div> - ); - } - else { + return <div key="time-text" className="animation-text" style={{ visibility: this.props.Document.isATOn ? 'visible' : 'hidden', display: this.props.Document.isATOn ? 'flex' : 'none' }}>{`Total: ${this.toReadTime(totalTime)}`}</div>; + } else { const ctime = `Current: ${this.getCurrentTime()}`; const ttime = `Total: ${this.toReadTime(this._time)}`; return ( - <div style={{ flexDirection: "column" }}> - <div className="animation-text" style={{ fontSize: "10px", width: "100%", display: !this.props.Document.isATOn ? "block" : "none" }}> + <div style={{ flexDirection: 'column' }}> + <div className="animation-text" style={{ fontSize: '10px', width: '100%', display: !this.props.Document.isATOn ? 'block' : 'none' }}> {ctime} </div> - <div className="animation-text" style={{ fontSize: "10px", width: "100%", display: !this.props.Document.isATOn ? "block" : "none" }}> + <div className="animation-text" style={{ fontSize: '10px', width: '100%', display: !this.props.Document.isATOn ? 'block' : 'none' }}> {ttime} </div> </div> @@ -402,7 +438,7 @@ export class Timeline extends React.Component<FieldViewProps> { e.preventDefault(); e.stopPropagation(); this.toggleHandle(); - } + }; /** * turns on the toggle button (the purple slide button that changes from editing mode and play mode @@ -415,23 +451,22 @@ export class Timeline extends React.Component<FieldViewProps> { this.props.Document.isATOn = !this.props.Document.isATOn; if (!BoolCast(this.props.Document.isATOn)) { //turning on playmode... - roundToggle.style.transform = "translate(0px, 0px)"; - roundToggle.style.animationName = "turnoff"; - roundToggleContainer.style.animationName = "turnoff"; - roundToggleContainer.style.backgroundColor = "white"; + roundToggle.style.transform = 'translate(0px, 0px)'; + roundToggle.style.animationName = 'turnoff'; + roundToggleContainer.style.animationName = 'turnoff'; + roundToggleContainer.style.backgroundColor = 'white'; timelineContainer.style.top = `${-this._containerHeight}px`; this.toPlay(); } else { //turning on authoring mode... - roundToggle.style.transform = "translate(20px, 0px)"; - roundToggle.style.animationName = "turnon"; - roundToggleContainer.style.animationName = "turnon"; - roundToggleContainer.style.backgroundColor = "#9acedf"; - timelineContainer.style.top = "0px"; + roundToggle.style.transform = 'translate(20px, 0px)'; + roundToggle.style.animationName = 'turnon'; + roundToggleContainer.style.animationName = 'turnon'; + roundToggleContainer.style.backgroundColor = '#9acedf'; + timelineContainer.style.top = '0px'; this.toAuthoring(); } - } - + }; @action.bound changeLengths() { @@ -443,9 +478,9 @@ export class Timeline extends React.Component<FieldViewProps> { // @computed getCurrentTime = () => { - const current = KeyframeFunc.convertPixelTime(this._currentBarX, "mili", "time", this._tickSpacing, this._tickIncrement); + const current = KeyframeFunc.convertPixelTime(this._currentBarX, 'mili', 'time', this._tickSpacing, this._tickIncrement); return this.toReadTime(current > this._time ? this._time : current); - } + }; @observable private mapOfTracks: (Track | null)[] = []; @@ -465,22 +500,22 @@ export class Timeline extends React.Component<FieldViewProps> { } }); return longestTime; - } + }; @action toAuthoring = () => { this._time = Math.ceil((this.findLongestTime() ?? 1) / 100000) * 100000; - this._totalLength = KeyframeFunc.convertPixelTime(this._time, "mili", "pixel", this._tickSpacing, this._tickIncrement); - } + this._totalLength = KeyframeFunc.convertPixelTime(this._time, 'mili', 'pixel', this._tickSpacing, this._tickIncrement); + }; @action toPlay = () => { this._time = this.findLongestTime(); - this._totalLength = KeyframeFunc.convertPixelTime(this._time, "mili", "pixel", this._tickSpacing, this._tickIncrement); - } + this._totalLength = KeyframeFunc.convertPixelTime(this._time, 'mili', 'pixel', this._tickSpacing, this._tickIncrement); + }; /** - * if you have any question here, just shoot me an email or text. + * if you have any question here, just shoot me an email or text. * basically the only thing you need to edit besides render methods in track (individual track lines) and keyframe (green region) */ render() { @@ -488,23 +523,46 @@ export class Timeline extends React.Component<FieldViewProps> { // change visible and total width return ( - <div style={{ visibility: "visible" }}> - <div key="timeline_wrapper" style={{ visibility: this.props.Document.isATOn ? "visible" : "hidden", left: "0px", top: "0px", position: "absolute", width: "100%", transform: "translate(0px, 0px)" }}> + <div style={{ visibility: 'visible' }}> + <div key="timeline_wrapper" style={{ visibility: this.props.Document.isATOn ? 'visible' : 'hidden', left: '0px', top: '0px', position: 'absolute', width: '100%', transform: 'translate(0px, 0px)' }}> <div key="timeline_container" className="timeline-container" ref={this._timelineContainer} style={{ height: `${this._containerHeight}px`, top: `0px` }}> <div key="timeline_info" className="info-container" onPointerDown={this.onPanDown} ref={this._infoContainer} onWheel={this.onWheelZoom}> {this.drawTicks()} <div key="timeline_scrubber" className="scrubber" style={{ transform: `translate(${this._currentBarX}px)` }}> - <div key="timeline_scrubberhead" className="scrubberhead" onPointerDown={this.onScrubberDown} ></div> + <div key="timeline_scrubberhead" className="scrubberhead" onPointerDown={this.onScrubberDown}></div> </div> <div key="timeline_trackbox" className="trackbox" ref={this._trackbox} style={{ width: `${this._totalLength}px` }}> - {this.children.map(doc => - <Track ref={ref => this.mapOfTracks.push(ref)} node={doc} currentBarX={this._currentBarX} changeCurrentBarX={this.changeCurrentBarX} transform={this.props.ScreenToLocalTransform()} time={this._time} tickSpacing={this._tickSpacing} tickIncrement={this._tickIncrement} collection={this.props.Document} timelineVisible={true} /> - )} + {this.children.map(doc => ( + <Track + ref={ref => this.mapOfTracks.push(ref)} + node={doc} + currentBarX={this._currentBarX} + changeCurrentBarX={this.changeCurrentBarX} + transform={this.props.ScreenToLocalTransform()} + time={this._time} + tickSpacing={this._tickSpacing} + tickIncrement={this._tickIncrement} + collection={this.props.Document} + timelineVisible={true} + /> + ))} </div> </div> <div className="currentTime">Current: {this.getCurrentTime()}</div> <div key="timeline_title" className="title-container" ref={this._titleContainer}> - {this.children.map(doc => <div style={{ height: `${(this._titleHeight)}px` }} className="datapane" onPointerOver={() => { Doc.BrushDoc(doc); }} onPointerOut={() => { Doc.UnBrushDoc(doc); }}><p>{doc.title}</p></div>)} + {this.children.map(doc => ( + <div + style={{ height: `${this._titleHeight}px` }} + className="datapane" + onPointerOver={() => { + Doc.BrushDoc(doc); + }} + onPointerOut={() => { + Doc.UnBrushDoc(doc); + }}> + <p>{StrCast(doc.title)}</p> + </div> + ))} </div> <div key="timeline_resize" onPointerDown={this.onResizeDown}> <FontAwesomeIcon className="resize" icon={faGripLines as IconLookup} /> @@ -515,4 +573,4 @@ export class Timeline extends React.Component<FieldViewProps> { </div> ); } -}
\ No newline at end of file +} diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 37589974b..7d40cab8c 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -1,37 +1,36 @@ -import React = require("react"); -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { CursorProperty } from "csstype"; -import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import { DataSym, Doc, HeightSym, Opt, WidthSym } from "../../../fields/Doc"; -import { Id } from "../../../fields/FieldSymbols"; -import { List } from "../../../fields/List"; -import { listSpec } from "../../../fields/Schema"; -import { SchemaHeaderField } from "../../../fields/SchemaHeaderField"; -import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types"; -import { TraceMobx } from "../../../fields/util"; -import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, returnZero, setupMoveUpEvents, smoothScroll, Utils } from "../../../Utils"; -import { Docs, DocUtils } from "../../documents/Documents"; -import { DragManager, dropActionType } from "../../util/DragManager"; -import { SnappingManager } from "../../util/SnappingManager"; -import { Transform } from "../../util/Transform"; -import { undoBatch } from "../../util/UndoManager"; -import { ContextMenu } from "../ContextMenu"; -import { ContextMenuProps } from "../ContextMenuItem"; -import { EditableView } from "../EditableView"; -import { LightboxView } from "../LightboxView"; -import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; -import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment } from "../nodes/DocumentView"; -import { StyleProp } from "../StyleProvider"; -import { CollectionMasonryViewFieldRow } from "./CollectionMasonryViewFieldRow"; -import "./CollectionStackingView.scss"; -import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn"; -import { CollectionSubView } from "./CollectionSubView"; -import { CollectionViewType } from "./CollectionView"; -import { FieldViewProps } from "../nodes/FieldView"; -import { FormattedTextBox } from "../nodes/formattedText/FormattedTextBox"; -const _global = (window /* browser */ || global /* node */) as any; - +import React = require('react'); +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { CursorProperty } from 'csstype'; +import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import { DataSym, Doc, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; +import { Id } from '../../../fields/FieldSymbols'; +import { List } from '../../../fields/List'; +import { listSpec } from '../../../fields/Schema'; +import { SchemaHeaderField } from '../../../fields/SchemaHeaderField'; +import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; +import { TraceMobx } from '../../../fields/util'; +import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils'; +import { Docs, DocUtils } from '../../documents/Documents'; +import { DragManager, dropActionType } from '../../util/DragManager'; +import { SnappingManager } from '../../util/SnappingManager'; +import { Transform } from '../../util/Transform'; +import { undoBatch } from '../../util/UndoManager'; +import { ContextMenu } from '../ContextMenu'; +import { ContextMenuProps } from '../ContextMenuItem'; +import { EditableView } from '../EditableView'; +import { LightboxView } from '../LightboxView'; +import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView'; +import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment } from '../nodes/DocumentView'; +import { StyleProp } from '../StyleProvider'; +import { CollectionMasonryViewFieldRow } from './CollectionMasonryViewFieldRow'; +import './CollectionStackingView.scss'; +import { CollectionStackingViewFieldColumn } from './CollectionStackingViewFieldColumn'; +import { CollectionSubView } from './CollectionSubView'; +import { CollectionViewType } from './CollectionView'; +import { FieldViewProps } from '../nodes/FieldView'; +import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; +const _global = (window /* browser */ || global) /* node */ as any; export type collectionStackingViewProps = { chromeHidden?: boolean; @@ -46,27 +45,50 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection _draggerRef = React.createRef<HTMLDivElement>(); _pivotFieldDisposer?: IReactionDisposer; _autoHeightDisposer?: IReactionDisposer; - _docXfs: { height: () => number, width: () => number, stackedDocTransform: () => Transform }[] = []; + _docXfs: { height: () => number; width: () => number; stackedDocTransform: () => Transform }[] = []; _columnStart: number = 0; @observable _heightMap = new Map<string, number>(); - @observable _cursor: CursorProperty = "grab"; + @observable _cursor: CursorProperty = 'grab'; @observable _scroll = 0; // used to force the document decoration to update when scrolling - @computed get chromeHidden() { return this.props.chromeHidden || BoolCast(this.layoutDoc.chromeHidden); } - @computed get columnHeaders() { return Cast(this.layoutDoc._columnHeaders, listSpec(SchemaHeaderField), null); } - @computed get pivotField() { return StrCast(this.layoutDoc._pivotField); } - @computed get filteredChildren() { return this.childLayoutPairs.filter(pair => (pair.layout instanceof Doc) && !pair.layout.hidden).map(pair => pair.layout); } - @computed get headerMargin() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin); } - @computed get xMargin() { return NumCast(this.layoutDoc._xMargin, 2 * Math.min(this.gridGap, .05 * this.props.PanelWidth())); } - @computed get yMargin() { return this.props.yPadding || NumCast(this.layoutDoc._yMargin, 5); } // 2 * this.gridGap)); } - @computed get gridGap() { return NumCast(this.layoutDoc._gridGap, 10); } - @computed get isStackingView() { return (this.props.viewType ?? this.layoutDoc._viewType) === CollectionViewType.Stacking; } - @computed get numGroupColumns() { return this.isStackingView ? Math.max(1, this.Sections.size + (this.showAddAGroup ? 1 : 0)) : 1; } - @computed get showAddAGroup() { return this.pivotField && !this.chromeHidden; } + @computed get chromeHidden() { + return this.props.chromeHidden || BoolCast(this.layoutDoc.chromeHidden); + } + @computed get columnHeaders() { + return Cast(this.layoutDoc._columnHeaders, listSpec(SchemaHeaderField), null); + } + @computed get pivotField() { + return StrCast(this.layoutDoc._pivotField); + } + @computed get filteredChildren() { + return this.childLayoutPairs.filter(pair => pair.layout instanceof Doc && !pair.layout.hidden).map(pair => pair.layout); + } + @computed get headerMargin() { + return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin); + } + @computed get xMargin() { + return NumCast(this.layoutDoc._xMargin, 2 * Math.min(this.gridGap, 0.05 * this.props.PanelWidth())); + } + @computed get yMargin() { + return this.props.yPadding || NumCast(this.layoutDoc._yMargin, 5); + } // 2 * this.gridGap)); } + @computed get gridGap() { + return NumCast(this.layoutDoc._gridGap, 10); + } + @computed get isStackingView() { + return (this.props.viewType ?? this.layoutDoc._viewType) === CollectionViewType.Stacking; + } + @computed get numGroupColumns() { + return this.isStackingView ? Math.max(1, this.Sections.size + (this.showAddAGroup ? 1 : 0)) : 1; + } + @computed get showAddAGroup() { + return this.pivotField && !this.chromeHidden; + } @computed get columnWidth() { - return Math.min(this.props.PanelWidth() - 2 * this.xMargin, - this.isStackingView ? Number.MAX_VALUE : this.layoutDoc._columnWidth === -1 ? this.props.PanelWidth() - 2 * this.xMargin : NumCast(this.layoutDoc._columnWidth, 250)); + return Math.min(this.props.PanelWidth() - 2 * this.xMargin, this.isStackingView ? Number.MAX_VALUE : this.layoutDoc._columnWidth === -1 ? this.props.PanelWidth() - 2 * this.xMargin : NumCast(this.layoutDoc._columnWidth, 250)); + } + @computed get NodeWidth() { + return this.props.PanelWidth() - this.gridGap; } - @computed get NodeWidth() { return this.props.PanelWidth() - this.gridGap; } constructor(props: any) { super(props); @@ -84,21 +106,23 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection const width = () => this.getDocWidth(d); const rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap); const style = this.isStackingView ? { width: width(), marginTop: i ? this.gridGap : 0, height: height() } : { gridRowEnd: `span ${rowSpan}` }; - return <div className={`collectionStackingView-${this.isStackingView ? "columnDoc" : "masonryDoc"}`} key={d[Id]} style={style} > - {this.getDisplayDoc(d, width)} - </div>; + return ( + <div className={`collectionStackingView-${this.isStackingView ? 'columnDoc' : 'masonryDoc'}`} key={d[Id]} style={style}> + {this.getDisplayDoc(d, width)} + </div> + ); }); - } + }; @action setDocHeight = (key: string, sectionHeight: number) => { this._heightMap.set(key, sectionHeight); - } + }; get Sections() { if (!this.pivotField || this.columnHeaders instanceof Promise) return new Map<SchemaHeaderField, Doc[]>(); if (this.columnHeaders === undefined) { - setTimeout(() => this.layoutDoc._columnHeaders = new List<SchemaHeaderField>(), 0); + setTimeout(() => (this.layoutDoc._columnHeaders = new List<SchemaHeaderField>()), 0); return new Map<SchemaHeaderField, Doc[]>(); } const columnHeaders = Array.from(this.columnHeaders); @@ -114,8 +138,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection const existingHeader = columnHeaders.find(sh => sh.heading === (castedSectionValue ? castedSectionValue.toString() : `NO ${this.pivotField.toUpperCase()} VALUE`)); if (existingHeader) { fields.get(existingHeader)!.push(d); - } - else { + } else { const newSchemaHeader = new SchemaHeaderField(castedSectionValue ? castedSectionValue.toString() : `NO ${this.pivotField.toUpperCase()} VALUE`); fields.set(newSchemaHeader, [d]); columnHeaders.push(newSchemaHeader); @@ -124,13 +147,19 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection }); // remove all empty columns if hideHeadings is set if (this.layoutDoc._columnsHideIfEmpty) { - Array.from(fields.keys()).filter(key => !fields.get(key)!.length).map(header => { - fields.delete(header); - columnHeaders.splice(columnHeaders.indexOf(header), 1); - changed = true; - }); + Array.from(fields.keys()) + .filter(key => !fields.get(key)!.length) + .map(header => { + fields.delete(header); + columnHeaders.splice(columnHeaders.indexOf(header), 1); + changed = true; + }); } - changed && setTimeout(action(() => this.columnHeaders?.splice(0, this.columnHeaders.length, ...columnHeaders)), 0); + changed && + setTimeout( + action(() => this.columnHeaders?.splice(0, this.columnHeaders.length, ...columnHeaders)), + 0 + ); return fields; } @@ -140,13 +169,19 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection // reset section headers when a new filter is inputted this._pivotFieldDisposer = reaction( () => this.pivotField, - () => this.layoutDoc._columnHeaders = new List() + () => (this.layoutDoc._columnHeaders = new List()) + ); + this._autoHeightDisposer = reaction( + () => this.layoutDoc._autoHeight, + autoHeight => + autoHeight && + this.props.setHeight?.( + Math.min( + NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), + this.headerMargin + (this.isStackingView ? Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace('px', '')))) : this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace('px', '')), 0)) + ) + ) ); - this._autoHeightDisposer = reaction(() => this.layoutDoc._autoHeight, - autoHeight => autoHeight && this.props.setHeight?.(Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), - this.headerMargin + (this.isStackingView ? - Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", "")))) : - this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0))))); } componentWillUnmount() { @@ -158,45 +193,50 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection @action moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean): boolean => { return this.props.removeDocument?.(doc) && addDocument?.(doc) ? true : false; - } + }; createRef = (ele: HTMLDivElement | null) => { this._masonryGridRef = ele; this.createDashEventsTarget(ele!); //so the whole grid is the drop target? - } + }; - @computed get onChildClickHandler() { return () => this.props.childClickScript || ScriptCast(this.Document.onChildClick); } - @computed get onChildDoubleClickHandler() { return () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); } + @computed get onChildClickHandler() { + return () => this.props.childClickScript || ScriptCast(this.Document.onChildClick); + } + @computed get onChildDoubleClickHandler() { + return () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); + } addDocTab = (doc: Doc, where: string) => { - if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) { + if (where === 'inPlace' && this.layoutDoc.isInPlaceContainer) { this.dataDoc[this.props.fieldKey] = new List<Doc>([doc]); return true; } return this.props.addDocTab(doc, where); - } + }; scrollToBottom = () => { smoothScroll(500, this._mainCont!, this._mainCont!.scrollHeight); - } + }; focusDocument = (doc: Doc, options?: DocFocusOptions) => { Doc.BrushDoc(doc); let focusSpeed = 0; - const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName("documentView-node")).find((node: any) => node.id === doc[Id]); + const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]); if (found) { const top = found.getBoundingClientRect().top; const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top); if (Math.floor(localTop[1]) !== 0) { - smoothScroll(focusSpeed = doc.presTransition || doc.presTransition === 0 ? NumCast(doc.presTransition) : 500, this._mainCont!, localTop[1] + this._mainCont!.scrollTop); + smoothScroll((focusSpeed = doc.presTransition || doc.presTransition === 0 ? NumCast(doc.presTransition) : 500), this._mainCont!, localTop[1] + this._mainCont!.scrollTop); } } const endFocus = async (moved: boolean) => options?.afterFocus?.(moved) ?? ViewAdjustment.doNothing; this.props.focus(this.rootDoc, { - willZoom: options?.willZoom, scale: options?.scale, afterFocus: (didFocus: boolean) => - new Promise<ViewAdjustment>(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed)) + willZoom: options?.willZoom, + scale: options?.scale, + afterFocus: (didFocus: boolean) => new Promise<ViewAdjustment>(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed)), }); - } + }; styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => { if (property === StyleProp.Opacity && doc) { @@ -208,85 +248,88 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection } } return this.props.styleProvider?.(doc, props, property); - } + }; @undoBatch @action onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => { const docView = fieldProps.DocumentView?.(); - if (docView && ["Enter"].includes(e.key) && e.ctrlKey) { + if (docView && ['Enter'].includes(e.key) && e.ctrlKey) { e.stopPropagation?.(); - const below = !e.altKey && e.key !== "Tab"; + const below = !e.altKey && e.key !== 'Tab'; const layoutKey = StrCast(docView.LayoutFieldKey); const newDoc = Doc.MakeCopy(docView.rootDoc, true); const dataField = docView.rootDoc[Doc.LayoutFieldKey(newDoc)]; newDoc[DataSym][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined; - if (layoutKey !== "layout" && docView.rootDoc[layoutKey] instanceof Doc) { + if (layoutKey !== 'layout' && docView.rootDoc[layoutKey] instanceof Doc) { newDoc[layoutKey] = docView.rootDoc[layoutKey]; } Doc.GetProto(newDoc).text = undefined; FormattedTextBox.SelectOnLoad = newDoc[Id]; return this.addDocument?.(newDoc); } - } + }; isContentActive = () => this.props.isSelected() || this.props.isContentActive(); - isChildContentActive = () => this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive)) ? true : undefined; + isChildContentActive = () => (this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive)) ? true : undefined); getDisplayDoc(doc: Doc, width: () => number) { - const dataDoc = (!doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS) ? undefined : this.props.DataDoc; + const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS ? undefined : this.props.DataDoc; const height = () => this.getDocHeight(doc); let dref: Opt<DocumentView>; const stackedDocTransform = () => this.getDocTransform(doc, dref); this._docXfs.push({ stackedDocTransform, width, height }); - return <DocumentView ref={r => dref = r || undefined} - Document={doc} - DataDoc={dataDoc || (!Doc.AreProtosEqual(doc[DataSym], doc) && doc[DataSym])} - renderDepth={this.props.renderDepth + 1} - PanelWidth={width} - PanelHeight={height} - styleProvider={this.styleProvider} - docViewPath={this.props.docViewPath} - fitWidth={this.props.childFitWidth} - isContentActive={this.isChildContentActive} - onKey={this.onKeyDown} - isDocumentActive={this.isContentActive} - LayoutTemplate={this.props.childLayoutTemplate} - LayoutTemplateString={this.props.childLayoutString} - NativeWidth={this.props.childIgnoreNativeSize ? returnZero : this.props.childFitWidth?.(doc) || doc._fitWidth && !Doc.NativeWidth(doc) ? width : undefined} // explicitly ignore nativeWidth/height if childIgnoreNativeSize is set- used by PresBox - NativeHeight={this.props.childIgnoreNativeSize ? returnZero : this.props.childFitWidth?.(doc) || doc._fitWidth && !Doc.NativeHeight(doc) ? height : undefined} - dontCenter={this.props.childIgnoreNativeSize ? "xy" : undefined} - dontRegisterView={dataDoc ? true : BoolCast(this.layoutDoc.childDontRegisterViews, this.props.dontRegisterView)} - rootSelected={this.rootSelected} - showTitle={this.props.childShowTitle} - dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType} - onClick={this.onChildClickHandler} - onDoubleClick={this.onChildDoubleClickHandler} - ScreenToLocalTransform={stackedDocTransform} - focus={this.focusDocument} - docFilters={this.childDocFilters} - hideDecorationTitle={this.props.childHideDecorationTitle?.()} - hideResizeHandles={this.props.childHideResizeHandles?.()} - hideTitle={this.props.childHideTitle?.()} - docRangeFilters={this.childDocRangeFilters} - searchFilterDocs={this.searchFilterDocs} - ContainingCollectionDoc={this.props.CollectionView?.props.Document} - ContainingCollectionView={this.props.CollectionView} - addDocument={this.props.addDocument} - moveDocument={this.props.moveDocument} - removeDocument={this.props.removeDocument} - contentPointerEvents={StrCast(this.layoutDoc.contentPointerEvents)} - whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} - addDocTab={this.addDocTab} - bringToFront={returnFalse} - scriptContext={this.props.scriptContext} - pinToPres={this.props.pinToPres} - />; + return ( + <DocumentView + ref={r => (dref = r || undefined)} + Document={doc} + DataDoc={dataDoc || (!Doc.AreProtosEqual(doc[DataSym], doc) && doc[DataSym])} + renderDepth={this.props.renderDepth + 1} + PanelWidth={width} + PanelHeight={height} + styleProvider={this.styleProvider} + docViewPath={this.props.docViewPath} + fitWidth={this.props.childFitWidth} + isContentActive={this.isChildContentActive} + onKey={this.onKeyDown} + isDocumentActive={this.isContentActive} + LayoutTemplate={this.props.childLayoutTemplate} + LayoutTemplateString={this.props.childLayoutString} + NativeWidth={this.props.childIgnoreNativeSize ? returnZero : this.props.childFitWidth?.(doc) || (doc._fitWidth && !Doc.NativeWidth(doc)) ? width : undefined} // explicitly ignore nativeWidth/height if childIgnoreNativeSize is set- used by PresBox + NativeHeight={this.props.childIgnoreNativeSize ? returnZero : this.props.childFitWidth?.(doc) || (doc._fitWidth && !Doc.NativeHeight(doc)) ? height : undefined} + dontCenter={this.props.childIgnoreNativeSize ? 'xy' : undefined} + dontRegisterView={dataDoc ? true : BoolCast(this.layoutDoc.childDontRegisterViews, this.props.dontRegisterView)} + rootSelected={this.rootSelected} + showTitle={this.props.childShowTitle} + dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType} + onClick={this.onChildClickHandler} + onDoubleClick={this.onChildDoubleClickHandler} + ScreenToLocalTransform={stackedDocTransform} + focus={this.focusDocument} + docFilters={this.childDocFilters} + hideDecorationTitle={this.props.childHideDecorationTitle?.()} + hideResizeHandles={this.props.childHideResizeHandles?.()} + hideTitle={this.props.childHideTitle?.()} + docRangeFilters={this.childDocRangeFilters} + searchFilterDocs={this.searchFilterDocs} + ContainingCollectionDoc={this.props.CollectionView?.props.Document} + ContainingCollectionView={this.props.CollectionView} + addDocument={this.props.addDocument} + moveDocument={this.props.moveDocument} + removeDocument={this.props.removeDocument} + contentPointerEvents={StrCast(this.layoutDoc.contentPointerEvents)} + whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} + addDocTab={this.addDocTab} + bringToFront={returnFalse} + scriptContext={this.props.scriptContext} + pinToPres={this.props.pinToPres} + /> + ); } getDocTransform(doc: Doc, dref?: DocumentView) { const y = this._scroll; // required for document decorations to update when the text box container is scrolled const { scale, translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv || undefined); - // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform. so we have to subtract that off - return new Transform(- translateX + (dref?.centeringX || 0), - translateY + (dref?.centeringY || 0), 1).scale(this.props.ScreenToLocalTransform().Scale); + // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform. so we have to subtract that off + return new Transform(-translateX + (dref?.centeringX || 0), -translateY + (dref?.centeringY || 0), 1).scale(this.props.ScreenToLocalTransform().Scale); } getDocWidth(d?: Doc) { if (!d) return 0; @@ -300,37 +343,42 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection getDocHeight(d?: Doc) { if (!d || d.hidden) return 0; const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.()); - const childDataDoc = (!d.isTemplateDoc && !d.isTemplateForField && !d.PARAMS) ? undefined : this.props.DataDoc; - const maxHeight = (lim => lim === 0 ? this.props.PanelWidth() : lim === -1 ? 10000 : lim)(NumCast(this.layoutDoc.childLimitHeight, -1)); + const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField && !d.PARAMS ? undefined : this.props.DataDoc; + const maxHeight = (lim => (lim === 0 ? this.props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1)); const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[WidthSym]() : 0); const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[HeightSym]() : 0); if (nw && nh) { const colWid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1); const docWid = this.layoutDoc._columnsFill ? colWid : Math.min(this.getDocWidth(d), colWid); - return Math.min( - maxHeight, - docWid * nh / nw); + return Math.min(maxHeight, (docWid * nh) / nw); } const childHeight = NumCast(childLayoutDoc._height); - const panelHeight = (childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? Number.MAX_SAFE_INTEGER : this.props.PanelHeight() - 2 * this.yMargin; + const panelHeight = childLayoutDoc._fitWidth || this.props.childFitWidth?.(d) ? Number.MAX_SAFE_INTEGER : this.props.PanelHeight() - 2 * this.yMargin; return Math.min(childHeight, maxHeight, panelHeight); } columnDividerDown = (e: React.PointerEvent) => { - runInAction(() => this._cursor = "grabbing"); - setupMoveUpEvents(this, e, this.onDividerMove, action(() => this._cursor = "grab"), emptyFunction); - } + runInAction(() => (this._cursor = 'grabbing')); + setupMoveUpEvents( + this, + e, + this.onDividerMove, + action(() => (this._cursor = 'grab')), + emptyFunction + ); + }; @action onDividerMove = (e: PointerEvent, down: number[], delta: number[]) => { this.layoutDoc._columnWidth = Math.max(10, this.columnWidth + delta[0]); return false; - } + }; @computed get columnDragger() { - return <div className="collectionStackingView-columnDragger" onPointerDown={this.columnDividerDown} ref={this._draggerRef} - style={{ cursor: this._cursor, left: `${this.columnWidth + this.xMargin}px`, top: `${Math.max(0, this.yMargin - 9)}px` }} > - <FontAwesomeIcon icon={"arrows-alt-h"} /> - </div>; + return ( + <div className="collectionStackingView-columnDragger" onPointerDown={this.columnDividerDown} ref={this._draggerRef} style={{ cursor: this._cursor, left: `${this.columnWidth + this.xMargin}px`, top: `${Math.max(0, this.yMargin - 9)}px` }}> + <FontAwesomeIcon icon={'arrows-alt-h'} /> + </div> + ); } @undoBatch @@ -341,7 +389,10 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection let dropAfter = 0; if (de.complete.docDragData) { this._docXfs.map((cd, i) => { - const pos = cd.stackedDocTransform().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap); + const pos = cd + .stackedDocTransform() + .inverse() + .transformPoint(-2 * this.gridGap, -2 * this.gridGap); const pos1 = cd.stackedDocTransform().inverse().transformPoint(cd.width(), cd.height()); if (where[0] > pos[0] && where[0] < pos1[0] && where[1] > pos[1] && (i === this._docXfs.length - 1 || where[1] < pos1[1])) { dropInd = i; @@ -358,21 +409,19 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection DragManager.docsBeingDragged = []; if (docs && newDocs.length) { const insertInd = dropInd === -1 ? docs.length : dropInd + dropAfter; - const offset = newDocs.reduce((off, ndoc) => this.filteredChildren.find((fdoc, i) => ndoc === fdoc && i < insertInd) ? off + 1 : off, 0); + const offset = newDocs.reduce((off, ndoc) => (this.filteredChildren.find((fdoc, i) => ndoc === fdoc && i < insertInd) ? off + 1 : off), 0); newDocs.filter(ndoc => docs.indexOf(ndoc) !== -1).forEach(ndoc => docs.splice(docs.indexOf(ndoc), 1)); docs.splice(insertInd - offset, 0, ...newDocs); } } - } - else if (de.complete.linkDragData?.dragDocument.context === this.props.Document && de.complete.linkDragData?.linkDragView?.props.CollectionFreeFormDocumentView?.()) { - const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, _fitWidth: true, title: "dropped annotation" }); + } else if (de.complete.linkDragData?.dragDocument.context === this.props.Document && de.complete.linkDragData?.linkDragView?.props.CollectionFreeFormDocumentView?.()) { + const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _fitWidth: true, title: 'dropped annotation' }); this.props.addDocument?.(source); - de.complete.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: de.complete.linkDragData.linkSourceGetAnchor() }, "doc annotation", ""); // TODODO this is where in text links get passed + de.complete.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: de.complete.linkDragData.linkSourceGetAnchor() }, 'doc annotation', ''); // TODODO this is where in text links get passed e.stopPropagation(); - } - else if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de.complete.annoDragData); + } else if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de.complete.annoDragData); return false; - } + }; @undoBatch internalAnchorAnnoDrop(e: Event, annoDragData: DragManager.AnchorAnnoDragData) { @@ -390,7 +439,10 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection const where = [e.clientX, e.clientY]; let targInd = -1; this._docXfs.map((cd, i) => { - const pos = cd.stackedDocTransform().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap); + const pos = cd + .stackedDocTransform() + .inverse() + .transformPoint(-2 * this.gridGap, -2 * this.gridGap); const pos1 = cd.stackedDocTransform().inverse().transformPoint(cd.width(), cd.height()); if (where[0] > pos[0] && where[0] < pos1[0] && where[1] > pos[1] && where[1] < pos1[1]) { targInd = i; @@ -406,98 +458,103 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection } } }); - } + }; headings = () => Array.from(this.Sections); refList: any[] = []; sectionStacking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => { const key = this.pivotField; - let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined; + let type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined = undefined; if (this.pivotField) { const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]); if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) { type = types[0]; } } - return <CollectionStackingViewFieldColumn - unobserveHeight={ref => this.refList.splice(this.refList.indexOf(ref), 1)} - observeHeight={ref => { - if (ref) { - this.refList.push(ref); - this.observer = new _global.ResizeObserver(action((entries: any) => { - if (this.layoutDoc._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) { - const height = this.headerMargin + - Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), - Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", ""))))); - if (!LightboxView.IsLightboxDocView(this.props.docViewPath())) { - this.props.setHeight?.(height); - } - } - })); - this.observer.observe(ref); - } - }} - addDocument={this.addDocument} - chromeHidden={this.chromeHidden} - columnHeaders={this.columnHeaders} - Document={this.props.Document} - DataDoc={this.props.DataDoc} - renderChildren={this.children} - columnWidth={this.columnWidth} - numGroupColumns={this.numGroupColumns} - gridGap={this.gridGap} - pivotField={this.pivotField} - key={heading?.heading ?? ""} - headings={this.headings} - heading={heading?.heading ?? ""} - headingObject={heading} - docList={docList} - yMargin={this.yMargin} - type={type} - createDropTarget={this.createDashEventsTarget} - screenToLocalTransform={this.props.ScreenToLocalTransform} - />; - } + return ( + <CollectionStackingViewFieldColumn + unobserveHeight={ref => this.refList.splice(this.refList.indexOf(ref), 1)} + observeHeight={ref => { + if (ref) { + this.refList.push(ref); + this.observer = new _global.ResizeObserver( + action((entries: any) => { + if (this.layoutDoc._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) { + const height = this.headerMargin + Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace('px', ''))))); + if (!LightboxView.IsLightboxDocView(this.props.docViewPath())) { + this.props.setHeight?.(height); + } + } + }) + ); + this.observer.observe(ref); + } + }} + addDocument={this.addDocument} + chromeHidden={this.chromeHidden} + columnHeaders={this.columnHeaders} + Document={this.props.Document} + DataDoc={this.props.DataDoc} + renderChildren={this.children} + columnWidth={this.columnWidth} + numGroupColumns={this.numGroupColumns} + gridGap={this.gridGap} + pivotField={this.pivotField} + key={heading?.heading ?? ''} + headings={this.headings} + heading={heading?.heading ?? ''} + headingObject={heading} + docList={docList} + yMargin={this.yMargin} + type={type} + createDropTarget={this.createDashEventsTarget} + screenToLocalTransform={this.props.ScreenToLocalTransform} + /> + ); + }; sectionMasonry = (heading: SchemaHeaderField | undefined, docList: Doc[], first: boolean) => { const key = this.pivotField; - let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined; + let type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined = undefined; const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]); if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) { type = types[0]; } - const rows = () => !this.isStackingView ? 1 : Math.max(1, Math.min(docList.length, - Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap)))); - return <CollectionMasonryViewFieldRow - showHandle={first} - Document={this.props.Document} - chromeHidden={this.chromeHidden} - pivotField={this.pivotField} - unobserveHeight={(ref) => this.refList.splice(this.refList.indexOf(ref), 1)} - observeHeight={(ref) => { - if (ref) { - this.refList.push(ref); - this.observer = new _global.ResizeObserver(action((entries: any) => { - if (this.layoutDoc._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) { - const height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0); - this.props.setHeight?.(this.headerMargin + height); - } - })); - this.observer.observe(ref); - } - }} - key={heading ? heading.heading : ""} - rows={rows} - headings={this.headings} - heading={heading ? heading.heading : ""} - headingObject={heading} - docList={docList} - parent={this} - type={type} - createDropTarget={this.createDashEventsTarget} - screenToLocalTransform={this.props.ScreenToLocalTransform} - setDocHeight={this.setDocHeight} - />; - } + const rows = () => (!this.isStackingView ? 1 : Math.max(1, Math.min(docList.length, Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap))))); + return ( + <CollectionMasonryViewFieldRow + showHandle={first} + Document={this.props.Document} + chromeHidden={this.chromeHidden} + pivotField={this.pivotField} + unobserveHeight={ref => this.refList.splice(this.refList.indexOf(ref), 1)} + observeHeight={ref => { + if (ref) { + this.refList.push(ref); + this.observer = new _global.ResizeObserver( + action((entries: any) => { + if (this.layoutDoc._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) { + const height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace('px', '')), 0); + this.props.setHeight?.(this.headerMargin + height); + } + }) + ); + this.observer.observe(ref); + } + }} + key={heading ? heading.heading : ''} + rows={rows} + headings={this.headings} + heading={heading ? heading.heading : ''} + headingObject={heading} + docList={docList} + parent={this} + type={type} + createDropTarget={this.createDashEventsTarget} + screenToLocalTransform={this.props.ScreenToLocalTransform} + setDocHeight={this.setDocHeight} + /> + ); + }; @action addGroup = (value: string) => { @@ -507,25 +564,25 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection return true; } return false; - } + }; sortFunc = (a: [SchemaHeaderField, Doc[]], b: [SchemaHeaderField, Doc[]]): 1 | -1 => { - const descending = StrCast(this.layoutDoc._columnsSort) === "descending"; + const descending = StrCast(this.layoutDoc._columnsSort) === 'descending'; const firstEntry = descending ? b : a; const secondEntry = descending ? a : b; return firstEntry[0].heading > secondEntry[0].heading ? 1 : -1; - } + }; onContextMenu = (e: React.MouseEvent): void => { // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout if (!e.isPropagationStopped()) { const subItems: ContextMenuProps[] = []; - subItems.push({ description: `${this.layoutDoc._columnsFill ? "Variable Size" : "Autosize"} Column`, event: () => this.layoutDoc._columnsFill = !this.layoutDoc._columnsFill, icon: "plus" }); - subItems.push({ description: `${this.layoutDoc._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" }); - subItems.push({ description: "Clear All", event: () => this.dataDoc.data = new List([]), icon: "times" }); - ContextMenu.Instance.addItem({ description: "Options...", subitems: subItems, icon: "eye" }); + subItems.push({ description: `${this.layoutDoc._columnsFill ? 'Variable Size' : 'Autosize'} Column`, event: () => (this.layoutDoc._columnsFill = !this.layoutDoc._columnsFill), icon: 'plus' }); + subItems.push({ description: `${this.layoutDoc._autoHeight ? 'Variable Height' : 'Auto Height'}`, event: () => (this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight), icon: 'plus' }); + subItems.push({ description: 'Clear All', event: () => (this.dataDoc.data = new List([])), icon: 'times' }); + ContextMenu.Instance.addItem({ description: 'Options...', subitems: subItems, icon: 'eye' }); } - } + }; @computed get renderedSections() { TraceMobx(); @@ -534,7 +591,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection const entries = Array.from(this.Sections.entries()); sections = this.layoutDoc._columnsSort ? entries.sort(this.sortFunc) : entries; } - return sections.map((section, i) => this.isStackingView ? this.sectionStacking(section[0], section[1]) : this.sectionMasonry(section[0], section[1], i === 0)); + return sections.map((section, i) => (this.isStackingView ? this.sectionStacking(section[0], section[1]) : this.sectionMasonry(section[0], section[1], i === 0))); } @computed get buttonMenu() { @@ -544,85 +601,90 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection const width: number = NumCast(menuDoc._width, 30); const height: number = NumCast(menuDoc._height, 30); console.log(menuDoc.title, width, height); - return (<div className="buttonMenu-docBtn" - style={{ width: width, height: height }}> - <DocumentView - Document={menuDoc} - DataDoc={menuDoc} - isContentActive={this.props.isContentActive} - isDocumentActive={returnTrue} - addDocument={this.props.addDocument} - moveDocument={this.props.moveDocument} - addDocTab={this.props.addDocTab} - pinToPres={emptyFunction} - rootSelected={this.props.isSelected} - removeDocument={this.props.removeDocument} - ScreenToLocalTransform={Transform.Identity} - PanelWidth={() => 35} - PanelHeight={() => 35} - renderDepth={this.props.renderDepth} - focus={emptyFunction} - styleProvider={this.props.styleProvider} - docViewPath={returnEmptyDoclist} - whenChildContentsActiveChanged={emptyFunction} - bringToFront={emptyFunction} - docFilters={this.props.docFilters} - docRangeFilters={this.props.docRangeFilters} - searchFilterDocs={this.props.searchFilterDocs} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} - /> - </div> + return ( + <div className="buttonMenu-docBtn" style={{ width: width, height: height }}> + <DocumentView + Document={menuDoc} + DataDoc={menuDoc} + isContentActive={this.props.isContentActive} + isDocumentActive={returnTrue} + addDocument={this.props.addDocument} + moveDocument={this.props.moveDocument} + addDocTab={this.props.addDocTab} + pinToPres={emptyFunction} + rootSelected={this.props.isSelected} + removeDocument={this.props.removeDocument} + ScreenToLocalTransform={Transform.Identity} + PanelWidth={() => 35} + PanelHeight={() => 35} + renderDepth={this.props.renderDepth} + focus={emptyFunction} + styleProvider={this.props.styleProvider} + docViewPath={returnEmptyDoclist} + whenChildContentsActiveChanged={emptyFunction} + bringToFront={emptyFunction} + docFilters={this.props.docFilters} + docRangeFilters={this.props.docRangeFilters} + searchFilterDocs={this.props.searchFilterDocs} + ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} + /> + </div> ); } } + @computed get nativeWidth() { + return this.props.NativeWidth?.() ?? Doc.NativeWidth(this.layoutDoc); + } + @computed get nativeHeight() { + return this.props.NativeHeight?.() ?? Doc.NativeHeight(this.layoutDoc); + } - @computed get nativeWidth() { return this.props.NativeWidth?.() ?? Doc.NativeWidth(this.layoutDoc); } - @computed get nativeHeight() { return this.props.NativeHeight?.() ?? Doc.NativeHeight(this.layoutDoc); } - - @computed get scaling() { return !this.nativeWidth ? 1 : this.props.PanelHeight() / this.nativeHeight; } + @computed get scaling() { + return !this.nativeWidth ? 1 : this.props.PanelHeight() / this.nativeHeight; + } - @computed get backgroundEvents() { return SnappingManager.GetIsDragging(); } + @computed get backgroundEvents() { + return SnappingManager.GetIsDragging(); + } observer: any; render() { TraceMobx(); const editableViewProps = { - GetValue: () => "", + GetValue: () => '', SetValue: this.addGroup, - contents: "+ ADD A GROUP" + contents: '+ ADD A GROUP', }; const buttonMenu = this.rootDoc.buttonMenu; const noviceExplainer = this.rootDoc.explainer; return ( <> - {buttonMenu || noviceExplainer ? <div className="documentButtonMenu"> - {buttonMenu ? this.buttonMenu : null} - {Doc.noviceMode && noviceExplainer ? - <div className="documentExplanation"> - {noviceExplainer} - </div> - : null - } - </div> : null} - <div className="collectionStackingMasonry-cont" > - <div className={this.isStackingView ? "collectionStackingView" : "collectionMasonryView"} + {buttonMenu || noviceExplainer ? ( + <div className="documentButtonMenu"> + {buttonMenu ? this.buttonMenu : null} + {Doc.noviceMode && noviceExplainer ? <div className="documentExplanation">{StrCast(noviceExplainer)}</div> : null} + </div> + ) : null} + <div className="collectionStackingMasonry-cont"> + <div + className={this.isStackingView ? 'collectionStackingView' : 'collectionMasonryView'} ref={this.createRef} style={{ - overflowY: this.props.isContentActive() ? "auto" : "hidden", + overflowY: this.props.isContentActive() ? 'auto' : 'hidden', background: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor), - pointerEvents: this.backgroundEvents ? "all" : undefined + pointerEvents: this.backgroundEvents ? 'all' : undefined, }} - onScroll={action(e => this._scroll = e.currentTarget.scrollTop)} + onScroll={action(e => (this._scroll = e.currentTarget.scrollTop))} onDrop={this.onExternalDrop.bind(this)} onContextMenu={this.onContextMenu} - onWheel={e => this.props.isContentActive(true) && e.stopPropagation()} > + onWheel={e => this.props.isContentActive(true) && e.stopPropagation()}> {this.renderedSections} - {!this.showAddAGroup ? (null) : - <div key={`${this.props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton" - style={{ width: !this.isStackingView ? "100%" : this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}> + {!this.showAddAGroup ? null : ( + <div key={`${this.props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton" style={{ width: !this.isStackingView ? '100%' : this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}> <EditableView {...editableViewProps} /> - </div>} + </div> + )} {/* {this.chromeHidden || !this.props.isSelected() ? (null) : <Switch onChange={this.onToggle} @@ -634,7 +696,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection </div> </div> </> - ); } } diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index ba72fb7b9..f5b9162d3 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -1,5 +1,5 @@ -import { action, computed, IReactionDisposer, observable, reaction } from "mobx"; -import { observer } from "mobx-react"; +import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; +import { observer } from 'mobx-react'; import { DataSym, Doc, DocListCast, HeightSym, Opt, StrListCast, WidthSym } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; @@ -11,27 +11,27 @@ import { emptyFunction, OmitKeys, returnEmptyDoclist, returnEmptyFilter, returnF import { DocUtils } from '../../documents/Documents'; import { CurrentUserUtils } from '../../util/CurrentUserUtils'; import { DocumentManager } from '../../util/DocumentManager'; -import { DragManager, dropActionType } from "../../util/DragManager"; +import { DragManager, dropActionType } from '../../util/DragManager'; import { SelectionManager } from '../../util/SelectionManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { undoBatch, UndoManager } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; -import { EditableView } from "../EditableView"; +import { EditableView } from '../EditableView'; import { DocumentView } from '../nodes/DocumentView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { StyleProp } from '../StyleProvider'; import { CollectionFreeFormView } from './collectionFreeForm'; -import { CollectionSubView } from "./CollectionSubView"; -import "./CollectionTreeView.scss"; -import { TreeView } from "./TreeView"; -import React = require("react"); -import { FieldViewProps } from "../nodes/FieldView"; -const _global = (window /* browser */ || global /* node */) as any; +import { CollectionSubView } from './CollectionSubView'; +import './CollectionTreeView.scss'; +import { TreeView } from './TreeView'; +import React = require('react'); +import { FieldViewProps } from '../nodes/FieldView'; +const _global = (window /* browser */ || global) /* node */ as any; export type collectionTreeViewProps = { - treeViewExpandedView?: "fields" | "layout" | "links" | "data"; + treeViewExpandedView?: 'fields' | 'layout' | 'links' | 'data'; treeViewOpen?: boolean; treeViewHideTitle?: boolean; treeViewHideHeaderFields?: boolean; @@ -45,9 +45,9 @@ export type collectionTreeViewProps = { }; export enum TreeViewType { - outline = "outline", - fileSystem = "fileSystem", - default = "default" + outline = 'outline', + fileSystem = 'fileSystem', + default = 'default', } @observer @@ -61,13 +61,28 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree private observer: any; // observer for monitoring tree view items. private static expandViewLabelSize = 20; - @computed get doc() { return this.props.Document; } - @computed get dataDoc() { return this.props.DataDoc || this.doc; } - @computed get treeViewtruncateTitleWidth() { return NumCast(this.doc.treeViewTruncateTitleWidth, this.panelWidth()); } - @computed get treeChildren() { TraceMobx(); return this.props.childDocuments || this.childDocs; } - @computed get outlineMode() { return this.doc.treeViewType === TreeViewType.outline; } - @computed get fileSysMode() { return this.doc.treeViewType === TreeViewType.fileSystem; } - @computed get dashboardMode() { return this.doc === CurrentUserUtils.MyDashboards; } + @computed get doc() { + return this.props.Document; + } + @computed get dataDoc() { + return this.props.DataDoc || this.doc; + } + @computed get treeViewtruncateTitleWidth() { + return NumCast(this.doc.treeViewTruncateTitleWidth, this.panelWidth()); + } + @computed get treeChildren() { + TraceMobx(); + return this.props.childDocuments || this.childDocs; + } + @computed get outlineMode() { + return this.doc.treeViewType === TreeViewType.outline; + } + @computed get fileSysMode() { + return this.doc.treeViewType === TreeViewType.fileSystem; + } + @computed get dashboardMode() { + return this.doc === CurrentUserUtils.MyDashboards; + } @observable _explainerHeight = 0; // height of the description of the tree view @@ -75,11 +90,11 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree // these should stay in synch with counterparts in DocComponent.ts ViewBoxAnnotatableComponent @observable _isAnyChildContentActive = false; - whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged(this._isAnyChildContentActive = isActive)); - isContentActive = (outsideReaction?: boolean) => (CurrentUserUtils.ActiveTool !== InkTool.None || - (this.props.isContentActive?.() || this.props.Document.forceActive || - this.props.isSelected(outsideReaction) || this._isAnyChildContentActive || - this.props.rootSelected(outsideReaction)) ? true : false) + whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive))); + isContentActive = (outsideReaction?: boolean) => + CurrentUserUtils.ActiveTool !== InkTool.None || this.props.isContentActive?.() || this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isAnyChildContentActive || this.props.rootSelected(outsideReaction) + ? true + : false; componentWillUnmount() { this._isDisposing = true; @@ -89,47 +104,51 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree } componentDidMount() { - this._disposers.autoheight = reaction(() => this.rootDoc.autoHeight, + this._disposers.autoheight = reaction( + () => this.rootDoc.autoHeight, auto => auto && this.computeHeight(), - { fireImmediately: true }); + { fireImmediately: true } + ); } computeHeight = () => { if (!this._isDisposing) { - const titleHeight = !this._titleRef ? this.marginTop() : Number(getComputedStyle(this._titleRef).height.replace("px", "")); - const bodyHeight = Array.from(this.refList).reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), this.marginBot()); + const titleHeight = !this._titleRef ? this.marginTop() : Number(getComputedStyle(this._titleRef).height.replace('px', '')); + const bodyHeight = Array.from(this.refList).reduce((p, r) => p + Number(getComputedStyle(r).height.replace('px', '')), this.marginBot()); this.layoutDoc._autoHeightMargins = bodyHeight; this.props.setHeight?.(bodyHeight + titleHeight); } - } + }; unobserveHeight = (ref: any) => { this.refList.delete(ref); this.rootDoc.autoHeight && this.computeHeight(); - } + }; observeHeight = (ref: any) => { if (ref) { this.refList.add(ref); - this.observer = new _global.ResizeObserver(action((entries: any) => { - if (this.rootDoc.autoHeight && ref && this.refList.size && !SnappingManager.GetIsDragging()) { - this.computeHeight(); - } - })); + this.observer = new _global.ResizeObserver( + action((entries: any) => { + if (this.rootDoc.autoHeight && ref && this.refList.size && !SnappingManager.GetIsDragging()) { + this.computeHeight(); + } + }) + ); this.rootDoc.autoHeight && this.computeHeight(); this.observer.observe(ref); } - } + }; protected createTreeDropTarget = (ele: HTMLDivElement) => { this._treedropDisposer?.(); - if (this._mainEle = ele) this._treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.doc, this.onInternalPreDrop.bind(this)); - } + if ((this._mainEle = ele)) this._treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.doc, this.onInternalPreDrop.bind(this)); + }; protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => { const dragData = de.complete.docDragData; if (dragData) { const isInTree = () => Doc.AreProtosEqual(dragData.treeViewDoc, this.props.Document) || dragData.draggedDocuments.some(d => d.context === this.doc && this.childDocs.includes(d)); - dragData.dropAction = targetAction && !isInTree() ? targetAction : this.doc === dragData?.treeViewDoc ? "same" : dragData.dropAction; + dragData.dropAction = targetAction && !isInTree() ? targetAction : this.doc === dragData?.treeViewDoc ? 'same' : dragData.dropAction; } - } + }; @action remove = (doc: Doc | Doc[]): boolean => { @@ -149,7 +168,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree return true; } return false; - } + }; @action addDoc = (docs: Doc | Doc[], relativeTo: Opt<Doc>, before?: boolean): boolean => { @@ -161,81 +180,85 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree }, true); if (this.doc.resolvedDataDoc instanceof Promise) return false; return relativeTo === undefined ? this.props.addDocument?.(docs) || false : doAddDoc(docs); - } + }; onContextMenu = (e: React.MouseEvent): void => { // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout if (!Doc.noviceMode) { const layoutItems: ContextMenuProps[] = []; - layoutItems.push({ description: "Make tree state " + (this.doc.treeViewOpenIsTransient ? "persistent" : "transient"), event: () => this.doc.treeViewOpenIsTransient = !this.doc.treeViewOpenIsTransient, icon: "paint-brush" }); - layoutItems.push({ description: (this.doc.treeViewHideHeaderFields ? "Show" : "Hide") + " Header Fields", event: () => this.doc.treeViewHideHeaderFields = !this.doc.treeViewHideHeaderFields, icon: "paint-brush" }); - layoutItems.push({ description: (this.doc.treeViewHideTitle ? "Show" : "Hide") + " Title", event: () => this.doc.treeViewHideTitle = !this.doc.treeViewHideTitle, icon: "paint-brush" }); - ContextMenu.Instance.addItem({ description: "Options...", subitems: layoutItems, icon: "eye" }); - const existingOnClick = ContextMenu.Instance.findByDescription("OnClick..."); - const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : []; - onClicks.push({ description: "Edit onChecked Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.doc, undefined, "onCheckedClick"), "edit onCheckedClick"), icon: "edit" }); - !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "mouse-pointer" }); + layoutItems.push({ description: 'Make tree state ' + (this.doc.treeViewOpenIsTransient ? 'persistent' : 'transient'), event: () => (this.doc.treeViewOpenIsTransient = !this.doc.treeViewOpenIsTransient), icon: 'paint-brush' }); + layoutItems.push({ description: (this.doc.treeViewHideHeaderFields ? 'Show' : 'Hide') + ' Header Fields', event: () => (this.doc.treeViewHideHeaderFields = !this.doc.treeViewHideHeaderFields), icon: 'paint-brush' }); + layoutItems.push({ description: (this.doc.treeViewHideTitle ? 'Show' : 'Hide') + ' Title', event: () => (this.doc.treeViewHideTitle = !this.doc.treeViewHideTitle), icon: 'paint-brush' }); + ContextMenu.Instance.addItem({ description: 'Options...', subitems: layoutItems, icon: 'eye' }); + const existingOnClick = ContextMenu.Instance.findByDescription('OnClick...'); + const onClicks: ContextMenuProps[] = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : []; + onClicks.push({ description: 'Edit onChecked Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.doc, undefined, 'onCheckedClick'), 'edit onCheckedClick'), icon: 'edit' }); + !existingOnClick && ContextMenu.Instance.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' }); } - } + }; onTreeDrop = (e: React.DragEvent, addDocs?: (docs: Doc[]) => void) => this.onExternalDrop(e, {}, addDocs); @undoBatch makeTextCollection = (childDocs: Doc[]) => { this.addDoc(TreeView.makeTextBullet(), childDocs.length ? childDocs[0] : undefined, true); - } + }; get editableTitle() { - return <EditableView - contents={this.dataDoc.title} - display={"block"} - maxHeight={72} - height={"auto"} - GetValue={() => StrCast(this.dataDoc.title)} - SetValue={undoBatch((value: string, shift: boolean, enter: boolean) => { - if (enter && this.props.Document.treeViewType === TreeViewType.outline) this.makeTextCollection(this.treeChildren); - this.dataDoc.title = value; - return true; - })} />; + return ( + <EditableView + contents={this.dataDoc.title} + display={'block'} + maxHeight={72} + height={'auto'} + GetValue={() => StrCast(this.dataDoc.title)} + SetValue={undoBatch((value: string, shift: boolean, enter: boolean) => { + if (enter && this.props.Document.treeViewType === TreeViewType.outline) this.makeTextCollection(this.treeChildren); + this.dataDoc.title = value; + return true; + })} + /> + ); } - onKey = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => { - if (this.outlineMode && e.key === "Enter") { + if (this.outlineMode && e.key === 'Enter') { e.stopPropagation(); this.makeTextCollection(this.treeChildren); return true; } - } + }; get documentTitle() { - return <FormattedTextBox - {...this.props} - fieldKey={"text"} - renderDepth={this.props.renderDepth + 1} - isContentActive={this.isContentActive} - isDocumentActive={this.isContentActive} - rootSelected={returnTrue} - forceAutoHeight={true} // needed to make the title resize even if the rest of the tree view is not autoHeight - PanelWidth={this.documentTitleWidth} - PanelHeight={this.documentTitleHeight} - scaling={returnOne} - onKey={this.onKey} - docFilters={returnEmptyFilter} - docRangeFilters={returnEmptyFilter} - searchFilterDocs={returnEmptyDoclist} - ContainingCollectionDoc={this.doc} - ContainingCollectionView={this.props.CollectionView} - addDocument={returnFalse} - moveDocument={returnFalse} - removeDocument={returnFalse} - whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} - bringToFront={returnFalse} - />; + return ( + <FormattedTextBox + {...this.props} + fieldKey={'text'} + renderDepth={this.props.renderDepth + 1} + isContentActive={this.isContentActive} + isDocumentActive={this.isContentActive} + rootSelected={returnTrue} + forceAutoHeight={true} // needed to make the title resize even if the rest of the tree view is not autoHeight + PanelWidth={this.documentTitleWidth} + PanelHeight={this.documentTitleHeight} + scaling={returnOne} + onKey={this.onKey} + docFilters={returnEmptyFilter} + docRangeFilters={returnEmptyFilter} + searchFilterDocs={returnEmptyDoclist} + ContainingCollectionDoc={this.doc} + ContainingCollectionView={this.props.CollectionView} + addDocument={returnFalse} + moveDocument={returnFalse} + removeDocument={returnFalse} + whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} + bringToFront={returnFalse} + /> + ); } childContextMenuItems = () => { const customScripts = Cast(this.doc.childContextMenuScripts, listSpec(ScriptField), []); const customFilters = Cast(this.doc.childContextMenuFilters, listSpec(ScriptField), []); const icons = StrListCast(this.doc.childContextMenuIcons); return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label })); - } + }; @computed get treeViewElements() { TraceMobx(); const dropAction = StrCast(this.doc.childDropAction) as dropActionType; @@ -266,36 +289,34 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree this.props.treeViewSkipFields, true, this.whenChildContentsActiveChanged, - this.props.dontRegisterView || Cast(this.props.Document.childDontRegisterViews, "boolean", null), + this.props.dontRegisterView || Cast(this.props.Document.childDontRegisterViews, 'boolean', null), this.observeHeight, this.unobserveHeight, this.childContextMenuItems(), //TODO: [AL] add these this.props.AddToMap, this.props.RemFromMap, - this.props.hierarchyIndex, + this.props.hierarchyIndex ); } @computed get titleBar() { - return this.dataDoc === null ? (null) : - <div className="collectionTreeView-titleBar" key={this.doc[Id]} - style={!this.outlineMode ? { paddingLeft: this.marginX(), paddingTop: this.marginTop() } : {}} - ref={r => this._titleRef = r}> + return this.dataDoc === null ? null : ( + <div className="collectionTreeView-titleBar" key={this.doc[Id]} style={!this.outlineMode ? { paddingLeft: this.marginX(), paddingTop: this.marginTop() } : {}} ref={r => (this._titleRef = r)}> {this.outlineMode ? this.documentTitle : this.editableTitle} - </div>; + </div> + ); } @computed get noviceExplainer() { - return !Doc.noviceMode || !this.rootDoc.explainer ? (null) : - <div className="documentExplanation"> {this.rootDoc.explainer} </div>; + return !Doc.noviceMode || !this.rootDoc.explainer ? null : <div className="documentExplanation"> {StrCast(this.rootDoc.explainer)} </div>; } return35 = () => 35; @computed get buttonMenu() { const menuDoc = Cast(this.rootDoc.buttonMenuDoc, Doc, null); // To create a multibutton menu add a CollectionLinearView - return !menuDoc ? null : - (<div className="buttonMenu-docBtn" style={{ width: NumCast(menuDoc._width, 30), height: NumCast(menuDoc._height, 30) }}> + return !menuDoc ? null : ( + <div className="buttonMenu-docBtn" style={{ width: NumCast(menuDoc._width, 30), height: NumCast(menuDoc._height, 30) }}> <DocumentView Document={menuDoc} DataDoc={menuDoc} @@ -322,11 +343,16 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree ContainingCollectionView={undefined} ContainingCollectionDoc={undefined} /> - </div>); + </div> + ); } - @computed get nativeWidth() { return Doc.NativeWidth(this.Document, undefined, true); } - @computed get nativeHeight() { return Doc.NativeHeight(this.Document, undefined, true); } + @computed get nativeWidth() { + return Doc.NativeWidth(this.Document, undefined, true); + } + @computed get nativeHeight() { + return Doc.NativeHeight(this.Document, undefined, true); + } @computed get contentScaling() { const nw = this.nativeWidth; @@ -347,68 +373,73 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree addAnnotationDocument = (doc: Doc | Doc[]) => this.props.CollectionView?.addDocument(doc, `${this.props.fieldKey}-annotations`) || false; remAnnotationDocument = (doc: Doc | Doc[]) => this.props.CollectionView?.removeDocument(doc, `${this.props.fieldKey}-annotations`) || false; moveAnnotationDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) => - this.props.CollectionView?.moveDocument(doc, targetCollection, addDocument, `${this.props.fieldKey}-annotations`) || false + this.props.CollectionView?.moveDocument(doc, targetCollection, addDocument, `${this.props.fieldKey}-annotations`) || false; contentFunc = () => { const background = () => this.props.styleProvider?.(this.doc, this.props, StyleProp.BackgroundColor); - const pointerEvents = () => !this.props.isContentActive() && !SnappingManager.GetIsDragging() ? "none" : undefined; - const titleBar = this.props.treeViewHideTitle || this.doc.treeViewHideTitle ? (null) : this.titleBar; + const pointerEvents = () => (!this.props.isContentActive() && !SnappingManager.GetIsDragging() ? 'none' : undefined); + const titleBar = this.props.treeViewHideTitle || this.doc.treeViewHideTitle ? null : this.titleBar; return [ - <div className="collectionTreeView-contents" key="tree" style={{ - ...(!titleBar ? { paddingLeft: this.marginX(), paddingTop: this.marginTop() } : {}), - overflow: "auto", - height: "100%"//this.layoutDoc._autoHeight ? "max-content" : "100%" - }} > + <div + className="collectionTreeView-contents" + key="tree" + style={{ + ...(!titleBar ? { paddingLeft: this.marginX(), paddingTop: this.marginTop() } : {}), + overflow: 'auto', + height: '100%', //this.layoutDoc._autoHeight ? "max-content" : "100%" + }}> {titleBar} - <div className="collectionTreeView-container" + <div + className="collectionTreeView-container" style={{ - transform: this.outlineMode ? `scale(${this.contentScaling})` : "", + transform: this.outlineMode ? `scale(${this.contentScaling})` : '', paddingLeft: `${this.marginX()}px`, - width: this.outlineMode ? `calc(${100 / this.contentScaling}%)` : "" + width: this.outlineMode ? `calc(${100 / this.contentScaling}%)` : '', }} onContextMenu={this.onContextMenu}> - {!this.buttonMenu && !this.noviceExplainer ? (null) : + {!this.buttonMenu && !this.noviceExplainer ? null : ( <div className="documentButtonMenu" ref={action((r: HTMLDivElement) => r && (this._explainerHeight = r.getBoundingClientRect().height))}> {this.buttonMenu} {this.noviceExplainer} </div> - } - <div className="collectionTreeView-dropTarget" + )} + <div + className="collectionTreeView-dropTarget" style={{ background: background(), height: `calc(100% - ${this._explainerHeight}px)`, - pointerEvents: pointerEvents() + pointerEvents: pointerEvents(), }} onWheel={e => e.stopPropagation()} onDrop={this.onTreeDrop} ref={r => !this.doc.treeViewHasOverlay && r && this.createTreeDropTarget(r)}> - <ul className={`no-indent${this.outlineMode ? "-outline" : ""}`} > - {this.treeViewElements} - </ul> - </div > + <ul className={`no-indent${this.outlineMode ? '-outline' : ''}`}>{this.treeViewElements}</ul> + </div> </div> - </div> + </div>, ]; - } + }; render() { TraceMobx(); - return !(this.doc instanceof Doc) || !this.treeChildren ? (null) : - this.doc.treeViewHasOverlay ? - <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit} - isAnnotationOverlay={true} - isAnnotationOverlayScrollable={true} - childDocumentsActive={this.props.isDocumentActive} - fieldKey={this.props.fieldKey + "-annotations"} - dropAction={"move"} - select={emptyFunction} - addDocument={this.addAnnotationDocument} - removeDocument={this.remAnnotationDocument} - moveDocument={this.moveAnnotationDocument} - bringToFront={emptyFunction} - renderDepth={this.props.renderDepth + 1} > - {this.contentFunc} - </CollectionFreeFormView> : - this.contentFunc(); + return !(this.doc instanceof Doc) || !this.treeChildren ? null : this.doc.treeViewHasOverlay ? ( + <CollectionFreeFormView + {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit} + isAnnotationOverlay={true} + isAnnotationOverlayScrollable={true} + childDocumentsActive={this.props.isDocumentActive} + fieldKey={this.props.fieldKey + '-annotations'} + dropAction={'move'} + select={emptyFunction} + addDocument={this.addAnnotationDocument} + removeDocument={this.remAnnotationDocument} + moveDocument={this.moveAnnotationDocument} + bringToFront={emptyFunction} + renderDepth={this.props.renderDepth + 1}> + {this.contentFunc} + </CollectionFreeFormView> + ) : ( + this.contentFunc() + ); } -}
\ No newline at end of file +} diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 2ae0c01ef..d788f9a77 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -1,5 +1,5 @@ import { computed, observable, runInAction } from 'mobx'; -import { observer } from "mobx-react"; +import { observer } from 'mobx-react'; import * as React from 'react'; import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app import { Doc, DocListCast } from '../../../fields/Doc'; @@ -14,66 +14,65 @@ import { BranchCreate, BranchTask } from '../../documents/Gitlike'; import { CurrentUserUtils } from '../../util/CurrentUserUtils'; import { ImageUtils } from '../../util/Import & Export/ImageUtils'; import { InteractionUtils } from '../../util/InteractionUtils'; -import { ContextMenu } from "../ContextMenu"; +import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent'; import { FieldView, FieldViewProps } from '../nodes/FieldView'; import { CollectionCarousel3DView } from './CollectionCarousel3DView'; import { CollectionCarouselView } from './CollectionCarouselView'; -import { CollectionDockingView } from "./CollectionDockingView"; +import { CollectionDockingView } from './CollectionDockingView'; import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; import { CollectionGridView } from './collectionGrid/CollectionGridView'; import { CollectionLinearView } from './collectionLinear'; import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView'; import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView'; import { CollectionPileView } from './CollectionPileView'; -import { CollectionSchemaView } from "./collectionSchema/CollectionSchemaView"; +import { CollectionSchemaView } from './collectionSchema/CollectionSchemaView'; import { CollectionStackingView } from './CollectionStackingView'; import { SubCollectionViewProps } from './CollectionSubView'; import { CollectionTimeView } from './CollectionTimeView'; -import { CollectionTreeView } from "./CollectionTreeView"; +import { CollectionTreeView } from './CollectionTreeView'; import './CollectionView.scss'; export const COLLECTION_BORDER_WIDTH = 2; const path = require('path'); export enum CollectionViewType { - Invalid = "invalid", - Freeform = "freeform", - Schema = "schema", - Docking = "docking", + Invalid = 'invalid', + Freeform = 'freeform', + Schema = 'schema', + Docking = 'docking', Tree = 'tree', - Stacking = "stacking", - Masonry = "masonry", - Multicolumn = "multicolumn", - Multirow = "multirow", - Time = "time", - Carousel = "carousel", - Carousel3D = "3D Carousel", - Linear = "linear", + Stacking = 'stacking', + Masonry = 'masonry', + Multicolumn = 'multicolumn', + Multirow = 'multirow', + Time = 'time', + Carousel = 'carousel', + Carousel3D = '3D Carousel', + Linear = 'linear', //Staff = "staff", - Map = "map", - Grid = "grid", - Pile = "pileup", - StackedTimeline = "stacked timeline" + Map = 'map', + Grid = 'grid', + Pile = 'pileup', + StackedTimeline = 'stacked timeline', } -export interface CollectionViewProps extends FieldViewProps { - isAnnotationOverlay?: boolean; // is the collection an annotation overlay (eg an overlay on an image/video/etc) +interface CollectionViewProps_ extends FieldViewProps { + isAnnotationOverlay?: boolean; // is the collection an annotation overlay (eg an overlay on an image/video/etc) isAnnotationOverlayScrollable?: boolean; // whether the annotation overlay can be vertically scrolled (just for tree views, currently) layoutEngine?: () => string; setPreviewCursor?: (func: (x: number, y: number, drag: boolean, hide: boolean) => void) => void; // property overrides for child documents - children?: never | (() => JSX.Element[]) | React.ReactNode; childDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explicit list (see LinkBox) - childDocumentsActive?: () => boolean;// whether child documents can be dragged if collection can be dragged (eg., in a when a Pile document is in startburst mode) + childDocumentsActive?: () => boolean; // whether child documents can be dragged if collection can be dragged (eg., in a when a Pile document is in startburst mode) childFitWidth?: (child: Doc) => boolean; childShowTitle?: () => string; childOpacity?: () => number; - childContextMenuItems?: () => { script: ScriptField, label: string }[]; + childContextMenuItems?: () => { script: ScriptField; label: string }[]; childHideTitle?: () => boolean; // whether to hide the documentdecorations title for children childHideDecorationTitle?: () => boolean; childHideResizeHandles?: () => boolean; - childLayoutTemplate?: () => (Doc | undefined);// specify a layout Doc template to use for children of the collection + childLayoutTemplate?: () => Doc | undefined; // specify a layout Doc template to use for children of the collection childLayoutString?: string; childIgnoreNativeSize?: boolean; childClickScript?: ScriptField; @@ -81,20 +80,25 @@ export interface CollectionViewProps extends FieldViewProps { //TODO: [AL] add these fields AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[]; RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[]; - hierarchyIndex?: number[]; // hierarchical index of a document up to the rendering root (primarily used for tree views) + hierarchyIndex?: number[]; // hierarchical index of a document up to the rendering root (primarily used for tree views) } +export interface CollectionViewProps extends React.PropsWithChildren<CollectionViewProps_> {} @observer export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & CollectionViewProps>() { - public static LayoutString(fieldStr: string) { return FieldView.LayoutString(CollectionView, fieldStr); } + public static LayoutString(fieldStr: string) { + return FieldView.LayoutString(CollectionView, fieldStr); + } @observable private static _safeMode = false; - public static SetSafeMode(safeMode: boolean) { this._safeMode = safeMode; } + public static SetSafeMode(safeMode: boolean) { + this._safeMode = safeMode; + } protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; constructor(props: any) { super(props); - runInAction(() => this._annotationKeySuffix = returnEmptyString); + runInAction(() => (this._annotationKeySuffix = returnEmptyString)); } get collectionViewType(): CollectionViewType | undefined { @@ -102,15 +106,17 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab if (CollectionView._safeMode) { switch (viewField) { case CollectionViewType.Freeform: - case CollectionViewType.Schema: return CollectionViewType.Tree; - case CollectionViewType.Invalid: return CollectionViewType.Freeform; + case CollectionViewType.Schema: + return CollectionViewType.Tree; + case CollectionViewType.Invalid: + return CollectionViewType.Freeform; } } return viewField as any as CollectionViewType; } showIsTagged = () => { - return (null); + return null; // this section would display an icon in the bototm right of a collection to indicate that all // photos had been processed through Google's content analysis API and Google's tags had been // assigned to the documents googlePhotosTags field. @@ -119,136 +125,171 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab // const allTagged = imageProtos.length > 0 && imageProtos.every(image => image.googlePhotosTags); // return !allTagged ? (null) : <img id={"google-tags"} src={"/assets/google_tags.png"} />; //this.isContentActive(); - } + }; - screenToLocalTransform = () => this.props.renderDepth ? this.props.ScreenToLocalTransform() : this.props.ScreenToLocalTransform().scale(this.props.PanelWidth() / this.bodyPanelWidth()); + screenToLocalTransform = () => (this.props.renderDepth ? this.props.ScreenToLocalTransform() : this.props.ScreenToLocalTransform().scale(this.props.PanelWidth() / this.bodyPanelWidth())); private renderSubView = (type: CollectionViewType | undefined, props: SubCollectionViewProps) => { TraceMobx(); if (type === undefined) return null; switch (type) { default: - case CollectionViewType.Freeform: return <CollectionFreeFormView key="collview" {...props} />; - case CollectionViewType.Schema: return <CollectionSchemaView key="collview" {...props} />; - case CollectionViewType.Docking: return <CollectionDockingView key="collview" {...props} />; - case CollectionViewType.Tree: return <CollectionTreeView key="collview" {...props} />; - case CollectionViewType.Multicolumn: return <CollectionMulticolumnView key="collview" {...props} />; - case CollectionViewType.Multirow: return <CollectionMultirowView key="collview" {...props} />; - case CollectionViewType.Linear: return <CollectionLinearView key="collview" {...props} />; - case CollectionViewType.Pile: return <CollectionPileView key="collview" {...props} />; - case CollectionViewType.Carousel: return <CollectionCarouselView key="collview" {...props} />; - case CollectionViewType.Carousel3D: return <CollectionCarousel3DView key="collview" {...props} />; - case CollectionViewType.Stacking: return <CollectionStackingView key="collview" {...props} />; - case CollectionViewType.Masonry: return <CollectionStackingView key="collview" {...props} />; - case CollectionViewType.Time: return <CollectionTimeView key="collview" {...props} />; - case CollectionViewType.Grid: return <CollectionGridView key="collview" {...props} />; + case CollectionViewType.Freeform: + return <CollectionFreeFormView key="collview" {...props} />; + case CollectionViewType.Schema: + return <CollectionSchemaView key="collview" {...props} />; + case CollectionViewType.Docking: + return <CollectionDockingView key="collview" {...props} />; + case CollectionViewType.Tree: + return <CollectionTreeView key="collview" {...props} />; + case CollectionViewType.Multicolumn: + return <CollectionMulticolumnView key="collview" {...props} />; + case CollectionViewType.Multirow: + return <CollectionMultirowView key="collview" {...props} />; + case CollectionViewType.Linear: + return <CollectionLinearView key="collview" {...props} />; + case CollectionViewType.Pile: + return <CollectionPileView key="collview" {...props} />; + case CollectionViewType.Carousel: + return <CollectionCarouselView key="collview" {...props} />; + case CollectionViewType.Carousel3D: + return <CollectionCarousel3DView key="collview" {...props} />; + case CollectionViewType.Stacking: + return <CollectionStackingView key="collview" {...props} />; + case CollectionViewType.Masonry: + return <CollectionStackingView key="collview" {...props} />; + case CollectionViewType.Time: + return <CollectionTimeView key="collview" {...props} />; + case CollectionViewType.Grid: + return <CollectionGridView key="collview" {...props} />; //case CollectionViewType.Staff: return <CollectionStaffView key="collview" {...props} />; } - } + }; setupViewTypes(category: string, func: (viewType: CollectionViewType) => Doc, addExtras: boolean) { const subItems: ContextMenuProps[] = []; - subItems.push({ description: "Freeform", event: () => func(CollectionViewType.Freeform), icon: "signature" }); + subItems.push({ description: 'Freeform', event: () => func(CollectionViewType.Freeform), icon: 'signature' }); if (addExtras && CollectionView._safeMode) { - ContextMenu.Instance.addItem({ description: "Test Freeform", event: () => func(CollectionViewType.Invalid), icon: "project-diagram" }); + ContextMenu.Instance.addItem({ description: 'Test Freeform', event: () => func(CollectionViewType.Invalid), icon: 'project-diagram' }); } - subItems.push({ description: "Schema", event: () => func(CollectionViewType.Schema), icon: "th-list" }); - subItems.push({ description: "Tree", event: () => func(CollectionViewType.Tree), icon: "tree" }); - subItems.push({ description: "Stacking", event: () => func(CollectionViewType.Stacking)._autoHeight = true, icon: "ellipsis-v" }); - subItems.push({ description: "Multicolumn", event: () => func(CollectionViewType.Multicolumn), icon: "columns" }); - subItems.push({ description: "Multirow", event: () => func(CollectionViewType.Multirow), icon: "columns" }); - subItems.push({ description: "Masonry", event: () => func(CollectionViewType.Masonry), icon: "columns" }); - subItems.push({ description: "Carousel", event: () => func(CollectionViewType.Carousel), icon: "columns" }); - subItems.push({ description: "3D Carousel", event: () => func(CollectionViewType.Carousel3D), icon: "columns" }); - !Doc.noviceMode && subItems.push({ description: "Pivot/Time", event: () => func(CollectionViewType.Time), icon: "columns" }); - !Doc.noviceMode && subItems.push({ description: "Map", event: () => func(CollectionViewType.Map), icon: "globe-americas" }); - subItems.push({ description: "Grid", event: () => func(CollectionViewType.Grid), icon: "th-list" }); + subItems.push({ description: 'Schema', event: () => func(CollectionViewType.Schema), icon: 'th-list' }); + subItems.push({ description: 'Tree', event: () => func(CollectionViewType.Tree), icon: 'tree' }); + subItems.push({ description: 'Stacking', event: () => (func(CollectionViewType.Stacking)._autoHeight = true), icon: 'ellipsis-v' }); + subItems.push({ description: 'Multicolumn', event: () => func(CollectionViewType.Multicolumn), icon: 'columns' }); + subItems.push({ description: 'Multirow', event: () => func(CollectionViewType.Multirow), icon: 'columns' }); + subItems.push({ description: 'Masonry', event: () => func(CollectionViewType.Masonry), icon: 'columns' }); + subItems.push({ description: 'Carousel', event: () => func(CollectionViewType.Carousel), icon: 'columns' }); + subItems.push({ description: '3D Carousel', event: () => func(CollectionViewType.Carousel3D), icon: 'columns' }); + !Doc.noviceMode && subItems.push({ description: 'Pivot/Time', event: () => func(CollectionViewType.Time), icon: 'columns' }); + !Doc.noviceMode && subItems.push({ description: 'Map', event: () => func(CollectionViewType.Map), icon: 'globe-americas' }); + subItems.push({ description: 'Grid', event: () => func(CollectionViewType.Grid), icon: 'th-list' }); if (!Doc.IsSystem(this.rootDoc) && !this.rootDoc.isGroup && !this.rootDoc.annotationOn) { const existingVm = ContextMenu.Instance.findByDescription(category); - const catItems = existingVm && "subitems" in existingVm ? existingVm.subitems : []; - catItems.push({ description: "Add a Perspective...", addDivider: true, noexpand: true, subitems: subItems, icon: "eye" }); - !existingVm && ContextMenu.Instance.addItem({ description: category, subitems: catItems, icon: "eye" }); + const catItems = existingVm && 'subitems' in existingVm ? existingVm.subitems : []; + catItems.push({ description: 'Add a Perspective...', addDivider: true, noexpand: true, subitems: subItems, icon: 'eye' }); + !existingVm && ContextMenu.Instance.addItem({ description: category, subitems: catItems, icon: 'eye' }); } } onContextMenu = (e: React.MouseEvent): void => { const cm = ContextMenu.Instance; if (e.nativeEvent.cancelBubble) return; // nested calls to React to render can cause the same event to trigger in the outer view even if the inner view has handled it. This avoid CollectionDockingView menu options from being added when the event has been handled by a sub-document. - if (cm && !e.isPropagationStopped() && this.rootDoc[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 - this.setupViewTypes("UI Controls...", vtype => { - const newRendition = Doc.MakeAlias(this.rootDoc); - newRendition._viewType = vtype; - this.props.addDocTab(newRendition, "add:right"); - return newRendition; - }, false); + if (cm && !e.isPropagationStopped() && this.rootDoc[Id] !== CurrentUserUtils.MainDocId) { + // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 + this.setupViewTypes( + 'UI Controls...', + vtype => { + const newRendition = Doc.MakeAlias(this.rootDoc); + newRendition._viewType = vtype; + this.props.addDocTab(newRendition, 'add:right'); + return newRendition; + }, + false + ); - const options = cm.findByDescription("Options..."); - const optionItems = options && "subitems" in options ? options.subitems : []; - !Doc.noviceMode ? optionItems.splice(0, 0, { description: `${this.rootDoc.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.rootDoc.forceActive = !this.rootDoc.forceActive, icon: "project-diagram" }) : null; + const options = cm.findByDescription('Options...'); + const optionItems = options && 'subitems' in options ? options.subitems : []; + !Doc.noviceMode ? optionItems.splice(0, 0, { description: `${this.rootDoc.forceActive ? 'Select' : 'Force'} Contents Active`, event: () => (this.rootDoc.forceActive = !this.rootDoc.forceActive), icon: 'project-diagram' }) : null; if (this.rootDoc.childLayout instanceof Doc) { - optionItems.push({ description: "View Child Layout", event: () => this.props.addDocTab(this.rootDoc.childLayout as Doc, "add:right"), icon: "project-diagram" }); + optionItems.push({ description: 'View Child Layout', event: () => this.props.addDocTab(this.rootDoc.childLayout as Doc, 'add:right'), icon: 'project-diagram' }); } if (this.rootDoc.childClickedOpenTemplateView instanceof Doc) { - optionItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.rootDoc.childClickedOpenTemplateView as Doc, "add:right"), icon: "project-diagram" }); + optionItems.push({ description: 'View Child Detailed Layout', event: () => this.props.addDocTab(this.rootDoc.childClickedOpenTemplateView as Doc, 'add:right'), icon: 'project-diagram' }); } - !Doc.noviceMode && optionItems.push({ description: `${this.rootDoc.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.rootDoc.isInPlaceContainer = !this.rootDoc.isInPlaceContainer, icon: "project-diagram" }); + !Doc.noviceMode && optionItems.push({ description: `${this.rootDoc.isInPlaceContainer ? 'Unset' : 'Set'} inPlace Container`, event: () => (this.rootDoc.isInPlaceContainer = !this.rootDoc.isInPlaceContainer), icon: 'project-diagram' }); if (!Doc.noviceMode) { optionItems.push({ - description: "Create Branch", event: async () => this.props.addDocTab(await BranchCreate(this.rootDoc), "add:right"), icon: "project-diagram" + description: 'Create Branch', + event: async () => this.props.addDocTab(await BranchCreate(this.rootDoc), 'add:right'), + icon: 'project-diagram', }); optionItems.push({ - description: "Pull Master", event: () => BranchTask(this.rootDoc, "pull"), icon: "project-diagram" + description: 'Pull Master', + event: () => BranchTask(this.rootDoc, 'pull'), + icon: 'project-diagram', }); optionItems.push({ - description: "Merge Branches", event: () => BranchTask(this.rootDoc, "merge"), icon: "project-diagram" + description: 'Merge Branches', + event: () => BranchTask(this.rootDoc, 'merge'), + icon: 'project-diagram', }); } if (this.Document._viewType === CollectionViewType.Docking) { - optionItems.push({ description: "Create Dashboard", event: () => CurrentUserUtils.createNewDashboard(), icon: "project-diagram" }); + optionItems.push({ description: 'Create Dashboard', event: () => CurrentUserUtils.createNewDashboard(), icon: 'project-diagram' }); } - !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "hand-point-right" }); + !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'hand-point-right' }); if (!Doc.noviceMode && !this.rootDoc.annotationOn) { - const existingOnClick = cm.findByDescription("OnClick..."); - const onClicks = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : []; - const funcs = [{ key: "onChildClick", name: "On Child Clicked" }, { key: "onChildDoubleClick", name: "On Child Double Clicked" }]; - funcs.map(func => onClicks.push({ - description: `Edit ${func.name} script`, icon: "edit", event: (obj: any) => { - const alias = Doc.MakeAlias(this.rootDoc); - DocUtils.makeCustomViewClicked(alias, undefined, func.key); - this.props.addDocTab(alias, "add:right"); - } - })); - DocListCast(Cast(Doc.UserDoc()["clickFuncs-child"], Doc, null).data).forEach(childClick => + const existingOnClick = cm.findByDescription('OnClick...'); + const onClicks = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : []; + const funcs = [ + { key: 'onChildClick', name: 'On Child Clicked' }, + { key: 'onChildDoubleClick', name: 'On Child Double Clicked' }, + ]; + funcs.map(func => + onClicks.push({ + description: `Edit ${func.name} script`, + icon: 'edit', + event: (obj: any) => { + const alias = Doc.MakeAlias(this.rootDoc); + DocUtils.makeCustomViewClicked(alias, undefined, func.key); + this.props.addDocTab(alias, 'add:right'); + }, + }) + ); + DocListCast(Cast(Doc.UserDoc()['clickFuncs-child'], Doc, null).data).forEach(childClick => onClicks.push({ description: `Set child ${childClick.title}`, - icon: "edit", - event: () => Doc.GetProto(this.rootDoc)[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data)), - })); - !Doc.IsSystem(this.rootDoc) && !existingOnClick && cm.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "mouse-pointer" }); + icon: 'edit', + event: () => (Doc.GetProto(this.rootDoc)[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data))), + }) + ); + !Doc.IsSystem(this.rootDoc) && !existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' }); } if (!Doc.noviceMode) { - const more = cm.findByDescription("More..."); - const moreItems = more && "subitems" in more ? more.subitems : []; - moreItems.push({ description: "Export Image Hierarchy", icon: "columns", event: () => ImageUtils.ExportHierarchyToFileSystem(this.rootDoc) }); - !more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" }); + const more = cm.findByDescription('More...'); + const moreItems = more && 'subitems' in more ? more.subitems : []; + moreItems.push({ description: 'Export Image Hierarchy', icon: 'columns', event: () => ImageUtils.ExportHierarchyToFileSystem(this.rootDoc) }); + !more && cm.addItem({ description: 'More...', subitems: moreItems, icon: 'hand-point-right' }); } } - } + }; bodyPanelWidth = () => this.props.PanelWidth(); childHideResizeHandles = () => this.props.childHideResizeHandles?.() ?? BoolCast(this.Document.childHideResizeHandles); childHideDecorationTitle = () => this.props.childHideDecorationTitle?.() ?? BoolCast(this.Document.childHideDecorationTitle); childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.rootDoc.childLayoutTemplate, Doc, null); - @computed get childLayoutString() { return StrCast(this.rootDoc.childLayoutString); } + @computed get childLayoutString() { + return StrCast(this.rootDoc.childLayoutString); + } isContentActive = (outsideReaction?: boolean) => { return this.props.isContentActive(); - } + }; render() { TraceMobx(); const props: SubCollectionViewProps = { @@ -268,10 +309,11 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab childHideDecorationTitle: this.childHideDecorationTitle, CollectionView: this, }; - return (<div className={"collectionView"} onContextMenu={this.onContextMenu} - style={{ pointerEvents: this.props.ContainingCollectionDoc?._viewType === CollectionViewType.Freeform && this.rootDoc._lockedPosition ? "none" : undefined }}> - {this.showIsTagged()} - {this.renderSubView(this.collectionViewType, props)} - </div>); + return ( + <div className={'collectionView'} onContextMenu={this.onContextMenu} style={{ pointerEvents: this.props.ContainingCollectionDoc?._viewType === CollectionViewType.Freeform && this.rootDoc._lockedPosition ? 'none' : undefined }}> + {this.showIsTagged()} + {this.renderSubView(this.collectionViewType, props)} + </div> + ); } } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index dacbb3508..8720c9097 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -1,25 +1,23 @@ -import { computed } from "mobx"; -import { observer } from "mobx-react"; -import { Id } from "../../../../fields/FieldSymbols"; -import { DocumentManager } from "../../../util/DocumentManager"; -import "./CollectionFreeFormLinksView.scss"; -import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView"; -import React = require("react"); +import { computed } from 'mobx'; +import { observer } from 'mobx-react'; +import { Id } from '../../../../fields/FieldSymbols'; +import { DocumentManager } from '../../../util/DocumentManager'; +import './CollectionFreeFormLinksView.scss'; +import { CollectionFreeFormLinkView } from './CollectionFreeFormLinkView'; +import React = require('react'); @observer -export class CollectionFreeFormLinksView extends React.Component { +export class CollectionFreeFormLinksView extends React.Component<React.PropsWithChildren<{}>> { @computed get uniqueConnections() { - return Array.from(new Set(DocumentManager.Instance.LinkedDocumentViews)).map(c => - <CollectionFreeFormLinkView key={c.l[Id]} A={c.a} B={c.b} LinkDocs={[c.l]} /> - ); + return Array.from(new Set(DocumentManager.Instance.LinkedDocumentViews)).map(c => <CollectionFreeFormLinkView key={c.l[Id]} A={c.a} B={c.b} LinkDocs={[c.l]} />); } render() { - return <div className="collectionfreeformlinksview-container"> - <svg className="collectionfreeformlinksview-svgCanvas"> - {this.uniqueConnections} - </svg> - {this.props.children} - </div>; + return ( + <div className="collectionfreeformlinksview-container"> + <svg className="collectionfreeformlinksview-svgCanvas">{this.uniqueConnections}</svg> + {this.props.children} + </div> + ); } -}
\ No newline at end of file +} diff --git a/src/client/views/collections/collectionGrid/Grid.tsx b/src/client/views/collections/collectionGrid/Grid.tsx index 3d2ed0cf9..3d1d87aa0 100644 --- a/src/client/views/collections/collectionGrid/Grid.tsx +++ b/src/client/views/collections/collectionGrid/Grid.tsx @@ -1,14 +1,13 @@ import * as React from 'react'; -import { observer } from "mobx-react"; +import { observer } from 'mobx-react'; -import "../../../../../node_modules/react-grid-layout/css/styles.css"; -import "../../../../../node_modules/react-resizable/css/styles.css"; +import '../../../../../node_modules/react-grid-layout/css/styles.css'; +import '../../../../../node_modules/react-resizable/css/styles.css'; import * as GridLayout from 'react-grid-layout'; import { Layout } from 'react-grid-layout'; export { Layout } from 'react-grid-layout'; - interface GridProps { width: number; nodeList: JSX.Element[] | null; @@ -29,9 +28,10 @@ interface GridProps { @observer export default class Grid extends React.Component<GridProps> { render() { - const compactType = this.props.compactType === "vertical" || this.props.compactType === "horizontal" ? this.props.compactType : null; + const compactType = this.props.compactType === 'vertical' || this.props.compactType === 'horizontal' ? this.props.compactType : null; return ( - <GridLayout className="layout" + <GridLayout + className="layout" layout={this.props.layout} cols={this.props.numCols} rowHeight={this.props.rowHeight} @@ -43,9 +43,8 @@ export default class Grid extends React.Component<GridProps> { useCSSTransforms={true} onLayoutChange={this.props.setLayout} preventCollision={this.props.preventCollision} - transformScale={1 / this.props.transformScale} // still doesn't work :( - margin={[this.props.margin, this.props.margin]} - > + transformScale={1 / this.props.transformScale} // still doesn't work :( ?? + margin={[this.props.margin, this.props.margin]}> {this.props.nodeList} </GridLayout> ); diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index c31267e87..8adfdc70b 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -9,7 +9,7 @@ import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types import { emptyFunction, returnEmptyDoclist, returnTrue, Utils } from '../../../../Utils'; import { DocUtils } from '../../../documents/Documents'; import { CurrentUserUtils } from '../../../util/CurrentUserUtils'; -import { DocumentManager } from "../../../util/DocumentManager"; +import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager } from '../../../util/DragManager'; import { Transform } from '../../../util/Transform'; import { Colors, Shadows } from '../../global/globalEnums'; @@ -21,13 +21,12 @@ import { StyleProp } from '../../StyleProvider'; import { CollectionStackedTimeline } from '../CollectionStackedTimeline'; import { CollectionSubView } from '../CollectionSubView'; import { CollectionViewType } from '../CollectionView'; -import "./CollectionLinearView.scss"; - +import './CollectionLinearView.scss'; /** - * CollectionLinearView is the class for rendering the horizontal collection + * CollectionLinearView is the class for rendering the horizontal collection * of documents, it useful for horizontal menus. It can either be expandable - * or not using the linearViewExpandable field. + * or not using the linearViewExpandable field. * It is used in the following locations: * - It is used in the popup menu on the bottom left (see docButtons() in MainView.tsx) * - It is used for the context sensitive toolbar at the top (see contMenuButtons() in CollectionMenu.tsx) @@ -48,45 +47,47 @@ export class CollectionLinearView extends CollectionSubView() { } componentDidMount() { - this._widthDisposer = reaction(() => 5 + (this.layoutDoc.linearViewIsExpanded ? this.childDocs.length * (this.rootDoc[HeightSym]()) : 10), + this._widthDisposer = reaction( + () => 5 + (this.layoutDoc.linearViewIsExpanded ? this.childDocs.length * this.rootDoc[HeightSym]() : 10), width => this.childDocs.length && (this.layoutDoc._width = width), { fireImmediately: true } ); this._selectedDisposer = reaction( () => NumCast(this.layoutDoc.selectedIndex), - (i) => runInAction(() => { - this._selectedIndex = i; - let selected: any = undefined; - this.childLayoutPairs.map(async (pair, ind) => { - const isSelected = this._selectedIndex === ind; - if (isSelected) { - selected = pair; - } - else { - ScriptCast(pair.layout.proto?.onPointerUp)?.script.run({ this: pair.layout.proto }, console.log); + i => + runInAction(() => { + this._selectedIndex = i; + let selected: any = undefined; + this.childLayoutPairs.map(async (pair, ind) => { + const isSelected = this._selectedIndex === ind; + if (isSelected) { + selected = pair; + } else { + ScriptCast(pair.layout.proto?.onPointerUp)?.script.run({ this: pair.layout.proto }, console.log); + } + }); + if (selected && selected.layout) { + ScriptCast(selected.layout.proto?.onPointerDown)?.script.run({ this: selected.layout.proto }, console.log); } - }); - if (selected && selected.layout) { - ScriptCast(selected.layout.proto?.onPointerDown)?.script.run({ this: selected.layout.proto }, console.log); - } - }), + }), { fireImmediately: true } ); } - protected createDashEventsTarget = (ele: HTMLDivElement | null) => { //used for stacking and masonry view + protected createDashEventsTarget = (ele: HTMLDivElement | null) => { + //used for stacking and masonry view this._dropDisposer && this._dropDisposer(); if (ele) { this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc); } - } + }; dimension = () => NumCast(this.rootDoc._height); // 2 * the padding getTransform = (ele: Opt<HTMLDivElement>) => { if (!ele) return Transform.Identity(); const { scale, translateX, translateY } = Utils.GetScreenTransform(ele); return new Transform(-translateX, -translateY, 1); - } + }; @action exitLongLinks = () => { @@ -99,28 +100,27 @@ export class CollectionLinearView extends CollectionSubView() { } DocumentLinksButton.StartLink = undefined; DocumentLinksButton.StartLinkView = undefined; - } + }; @action changeDescriptionSetting = () => { if (LinkDescriptionPopup.showDescriptions) { - if (LinkDescriptionPopup.showDescriptions === "ON") { - LinkDescriptionPopup.showDescriptions = "OFF"; + if (LinkDescriptionPopup.showDescriptions === 'ON') { + LinkDescriptionPopup.showDescriptions = 'OFF'; LinkDescriptionPopup.descriptionPopup = false; } else { - LinkDescriptionPopup.showDescriptions = "ON"; + LinkDescriptionPopup.showDescriptions = 'ON'; } } else { - LinkDescriptionPopup.showDescriptions = "OFF"; + LinkDescriptionPopup.showDescriptions = 'OFF'; LinkDescriptionPopup.descriptionPopup = false; } - } + }; myContextMenu = (e: React.MouseEvent) => { e.stopPropagation(); e.preventDefault(); - } - + }; getDisplayDoc = (doc: Doc, preview: boolean = false) => { const nested = doc._viewType === CollectionViewType.Linear; @@ -129,44 +129,50 @@ export class CollectionLinearView extends CollectionSubView() { let dref: Opt<HTMLDivElement>; const docXf = () => this.getTransform(dref); // const scalable = pair.layout.onClick || pair.layout.onDragStart; - return hidden ? (null) : <div className={preview ? "preview" : `collectionLinearView-docBtn`} key={doc[Id]} ref={r => dref = r || undefined} - style={{ - pointerEvents: "all", - width: nested ? undefined : NumCast(doc._width), - height: nested ? undefined : NumCast(doc._height), - marginLeft: !nested ? 2.5 : 0, - marginRight: !nested ? 2.5 : 0, - // width: NumCast(pair.layout._width), - // height: NumCast(pair.layout._height), - }} > - <DocumentView - Document={doc} - isContentActive={this.props.isContentActive} - isDocumentActive={returnTrue} - addDocument={this.props.addDocument} - moveDocument={this.props.moveDocument} - addDocTab={this.props.addDocTab} - pinToPres={emptyFunction} - rootSelected={this.props.isSelected} - removeDocument={this.props.removeDocument} - ScreenToLocalTransform={docXf} - PanelWidth={nested ? doc[WidthSym] : this.dimension} - PanelHeight={nested || doc._height ? doc[HeightSym] : this.dimension} - renderDepth={this.props.renderDepth + 1} - dontRegisterView={BoolCast(this.rootDoc.childDontRegisterViews)} - focus={emptyFunction} - styleProvider={this.props.styleProvider} - docViewPath={returnEmptyDoclist} - whenChildContentsActiveChanged={emptyFunction} - bringToFront={emptyFunction} - docFilters={this.props.docFilters} - docRangeFilters={this.props.docRangeFilters} - searchFilterDocs={this.props.searchFilterDocs} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} - hideResizeHandles={true} /> - </div>; - } + return hidden ? null : ( + <div + className={preview ? 'preview' : `collectionLinearView-docBtn`} + key={doc[Id]} + ref={r => (dref = r || undefined)} + style={{ + pointerEvents: 'all', + width: nested ? undefined : NumCast(doc._width), + height: nested ? undefined : NumCast(doc._height), + marginLeft: !nested ? 2.5 : 0, + marginRight: !nested ? 2.5 : 0, + // width: NumCast(pair.layout._width), + // height: NumCast(pair.layout._height), + }}> + <DocumentView + Document={doc} + isContentActive={this.props.isContentActive} + isDocumentActive={returnTrue} + addDocument={this.props.addDocument} + moveDocument={this.props.moveDocument} + addDocTab={this.props.addDocTab} + pinToPres={emptyFunction} + rootSelected={this.props.isSelected} + removeDocument={this.props.removeDocument} + ScreenToLocalTransform={docXf} + PanelWidth={nested ? doc[WidthSym] : this.dimension} + PanelHeight={nested || doc._height ? doc[HeightSym] : this.dimension} + renderDepth={this.props.renderDepth + 1} + dontRegisterView={BoolCast(this.rootDoc.childDontRegisterViews)} + focus={emptyFunction} + styleProvider={this.props.styleProvider} + docViewPath={returnEmptyDoclist} + whenChildContentsActiveChanged={emptyFunction} + bringToFront={emptyFunction} + docFilters={this.props.docFilters} + docRangeFilters={this.props.docRangeFilters} + searchFilterDocs={this.props.searchFilterDocs} + ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} + hideResizeHandles={true} + /> + </div> + ); + }; render() { const guid = Utils.GenerateGuid(); // Generate a unique ID to use as the label @@ -178,66 +184,99 @@ export class CollectionLinearView extends CollectionSubView() { const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); const icon: string = StrCast(this.props.Document.icon); // Menu opener toggle - const menuOpener = <label htmlFor={`${guid}`} - style={{ - boxShadow: floating ? Shadows.STANDARD_SHADOW : undefined, - color: BoolCast(this.layoutDoc.linearViewIsExpanded) ? undefined : Colors.BLACK, - backgroundColor: backgroundColor === color ? "black" : BoolCast(this.layoutDoc.linearViewIsExpanded) ? undefined : Colors.LIGHT_GRAY - }} - onPointerDown={e => e.stopPropagation()} > - <div className="collectionLinearView-menuOpener"> - {BoolCast(this.layoutDoc.linearViewIsExpanded) ? icon ? icon : <FontAwesomeIcon icon={"minus"} /> : icon ? icon : <FontAwesomeIcon icon={"plus"} />} - </div> - </label>; - - return <div className={`collectionLinearView-outer ${this.layoutDoc.linearViewSubMenu}`} style={{ backgroundColor: BoolCast(this.layoutDoc.linearViewIsExpanded) ? undefined : "transparent" }}> - <div className="collectionLinearView" ref={this.createDashEventsTarget} - onContextMenu={this.myContextMenu} > - {!expandable ? (null) : <Tooltip title={<><div className="dash-tooltip">{BoolCast(this.props.Document.linearViewIsExpanded) ? "Close" : "Open"}</div></>} placement="top"> - {menuOpener} - </Tooltip>} - <input id={`${guid}`} type="checkbox" checked={BoolCast(this.props.Document.linearViewIsExpanded)} ref={this.addMenuToggle} - onChange={action(() => this.props.Document.linearViewIsExpanded = this.addMenuToggle.current!.checked)} /> - - <div className="collectionLinearView-content" - style={{ - height: this.dimension(), - flexDirection: flexDir, - gap: flexGap - }}> - {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))} - </div> - {!DocumentLinksButton.StartLink || this.layoutDoc !== CurrentUserUtils.MyDockedBtns ? null : - <span className="bottomPopup-background" style={{ pointerEvents: "all" }} - onPointerDown={e => e.stopPropagation()} > - <span className="bottomPopup-text" > - Creating link from: <b>{DocumentLinksButton.AnnotationId ? "Annotation in " : " "} {StrCast(DocumentLinksButton.StartLink.title).length < 51 ? DocumentLinksButton.StartLink.title : StrCast(DocumentLinksButton.StartLink.title).slice(0, 50) + '...'}</b> - </span> + const menuOpener = ( + <label + htmlFor={`${guid}`} + style={{ + boxShadow: floating ? Shadows.STANDARD_SHADOW : undefined, + color: BoolCast(this.layoutDoc.linearViewIsExpanded) ? undefined : Colors.BLACK, + backgroundColor: backgroundColor === color ? 'black' : BoolCast(this.layoutDoc.linearViewIsExpanded) ? undefined : Colors.LIGHT_GRAY, + }} + onPointerDown={e => e.stopPropagation()}> + <div className="collectionLinearView-menuOpener">{BoolCast(this.layoutDoc.linearViewIsExpanded) ? icon ? icon : <FontAwesomeIcon icon={'minus'} /> : icon ? icon : <FontAwesomeIcon icon={'plus'} />}</div> + </label> + ); - <Tooltip title={<><div className="dash-tooltip">{"Toggle description pop-up"} </div></>} placement="top"> - <span className="bottomPopup-descriptions" onClick={this.changeDescriptionSetting}> - Labels: {LinkDescriptionPopup.showDescriptions ? LinkDescriptionPopup.showDescriptions : "ON"} - </span> + return ( + <div className={`collectionLinearView-outer ${this.layoutDoc.linearViewSubMenu}`} style={{ backgroundColor: BoolCast(this.layoutDoc.linearViewIsExpanded) ? undefined : 'transparent' }}> + <div className="collectionLinearView" ref={this.createDashEventsTarget} onContextMenu={this.myContextMenu}> + {!expandable ? null : ( + <Tooltip + title={ + <> + <div className="dash-tooltip">{BoolCast(this.props.Document.linearViewIsExpanded) ? 'Close' : 'Open'}</div> + </> + } + placement="top"> + {menuOpener} </Tooltip> + )} + <input + id={`${guid}`} + type="checkbox" + checked={BoolCast(this.props.Document.linearViewIsExpanded)} + ref={this.addMenuToggle} + onChange={action(() => (this.props.Document.linearViewIsExpanded = this.addMenuToggle.current!.checked))} + /> - <Tooltip title={<><div className="dash-tooltip">Exit linking mode</div></>} placement="top"> - <span className="bottomPopup-exit" onClick={this.exitLongLinks}> - Stop + <div + className="collectionLinearView-content" + style={{ + height: this.dimension(), + flexDirection: flexDir, + gap: flexGap, + }}> + {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))} + </div> + {!DocumentLinksButton.StartLink || this.layoutDoc !== CurrentUserUtils.MyDockedBtns ? null : ( + <span className="bottomPopup-background" style={{ pointerEvents: 'all' }} onPointerDown={e => e.stopPropagation()}> + <span className="bottomPopup-text"> + Creating link from:{' '} + <b> + {(DocumentLinksButton.AnnotationId ? 'Annotation in ' : ' ') + + (StrCast(DocumentLinksButton.StartLink.title).length < 51 ? DocumentLinksButton.StartLink.title : StrCast(DocumentLinksButton.StartLink.title).slice(0, 50) + '...')} + </b> </span> - </Tooltip> - </span>} - {!CollectionStackedTimeline.CurrentlyPlaying || !CollectionStackedTimeline.CurrentlyPlaying.length || this.layoutDoc !== CurrentUserUtils.MyDockedBtns ? (null) : - <span className="bottomPopup-background"> - <span className="bottomPopup-text"> - Currently playing: - {CollectionStackedTimeline.CurrentlyPlaying.map((clip, i) => - <span className="audio-title" onPointerDown={() => DocumentManager.Instance.jumpToDocument(clip, true, undefined, [])}> - {clip.title + (i === CollectionStackedTimeline.CurrentlyPlaying.length - 1 ? "" : ",")} - </span>)} - </span> - </span>} + <Tooltip + title={ + <> + <div className="dash-tooltip">{'Toggle description pop-up'} </div> + </> + } + placement="top"> + <span className="bottomPopup-descriptions" onClick={this.changeDescriptionSetting}> + Labels: {LinkDescriptionPopup.showDescriptions ? LinkDescriptionPopup.showDescriptions : 'ON'} + </span> + </Tooltip> + + <Tooltip + title={ + <> + <div className="dash-tooltip">Exit linking mode</div> + </> + } + placement="top"> + <span className="bottomPopup-exit" onClick={this.exitLongLinks}> + Stop + </span> + </Tooltip> + </span> + )} + {!CollectionStackedTimeline.CurrentlyPlaying || !CollectionStackedTimeline.CurrentlyPlaying.length || this.layoutDoc !== CurrentUserUtils.MyDockedBtns ? null : ( + <span className="bottomPopup-background"> + <span className="bottomPopup-text"> + Currently playing: + {CollectionStackedTimeline.CurrentlyPlaying.map((clip, i) => ( + <span className="audio-title" onPointerDown={() => DocumentManager.Instance.jumpToDocument(clip, true, undefined, [])}> + {clip.title + (i === CollectionStackedTimeline.CurrentlyPlaying.length - 1 ? '' : ',')} + </span> + ))} + </span> + </span> + )} + </div> </div> - </div>; + ); } -}
\ No newline at end of file +} diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx index 1414bfdf7..ba301962b 100644 --- a/src/client/views/linking/LinkEditor.tsx +++ b/src/client/views/linking/LinkEditor.tsx @@ -1,15 +1,14 @@ -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Tooltip } from "@material-ui/core"; -import { action, computed, observable } from "mobx"; -import { observer } from "mobx-react"; -import { Doc, NumListCast, StrListCast, Field } from "../../../fields/Doc"; -import { DateCast, StrCast, Cast } from "../../../fields/Types"; -import { LinkManager } from "../../util/LinkManager"; -import { undoBatch } from "../../util/UndoManager"; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Tooltip } from '@material-ui/core'; +import { action, computed, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import { Doc, NumListCast, StrListCast, Field } from '../../../fields/Doc'; +import { DateCast, StrCast, Cast } from '../../../fields/Types'; +import { LinkManager } from '../../util/LinkManager'; +import { undoBatch } from '../../util/UndoManager'; import './LinkEditor.scss'; -import { LinkRelationshipSearch } from "./LinkRelationshipSearch"; -import React = require("react"); - +import { LinkRelationshipSearch } from './LinkRelationshipSearch'; +import React = require('react'); interface LinkEditorProps { sourceDoc: Doc; @@ -19,16 +18,20 @@ interface LinkEditorProps { } @observer export class LinkEditor extends React.Component<LinkEditorProps> { - @observable description = Field.toString(LinkManager.currentLink?.description as any as Field); @observable relationship = StrCast(LinkManager.currentLink?.linkRelationship); @observable zoomFollow = StrCast(this.props.sourceDoc.followLinkZoom); @observable openDropdown: boolean = false; @observable showInfo: boolean = false; - @computed get infoIcon() { if (this.showInfo) { return "chevron-up"; } return "chevron-down"; } - @observable private buttonColor: string = ""; - @observable private relationshipButtonColor: string = ""; - @observable private relationshipSearchVisibility: string = "none"; + @computed get infoIcon() { + if (this.showInfo) { + return 'chevron-up'; + } + return 'chevron-down'; + } + @observable private buttonColor: string = ''; + @observable private relationshipButtonColor: string = ''; + @observable private relationshipSearchVisibility: string = 'none'; @observable private searchIsActive: boolean = false; //@observable description = this.props.linkDoc.description ? StrCast(this.props.linkDoc.description) : "DESCRIPTION"; @@ -37,7 +40,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> { deleteLink = (): void => { LinkManager.Instance.deleteLink(this.props.linkDoc); this.props.showLinks(); - } + }; @undoBatch setRelationshipValue = action((value: string) => { @@ -49,11 +52,11 @@ export class LinkEditor extends React.Component<LinkEditorProps> { const linkRelationshipSizes = NumListCast(Doc.UserDoc().linkRelationshipSizes); const linkColorList = StrListCast(Doc.UserDoc().linkColorList); - // if the relationship does not exist in the list, add it and a corresponding unique randomly generated color + // if the relationship does not exist in the list, add it and a corresponding unique randomly generated color if (!linkRelationshipList?.includes(value)) { linkRelationshipList.push(value); linkRelationshipSizes.push(1); - const randColor = "rgb(" + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + ")"; + const randColor = 'rgb(' + Math.floor(Math.random() * 255) + ',' + Math.floor(Math.random() * 255) + ',' + Math.floor(Math.random() * 255) + ')'; linkColorList.push(randColor); // if the relationship is already in the list AND the new rel is different from the prev rel, update the rel sizes } else if (linkRelationshipList && value !== prevRelationship) { @@ -61,20 +64,22 @@ export class LinkEditor extends React.Component<LinkEditorProps> { //increment size of new relationship size if (index !== -1 && index < linkRelationshipSizes.length) { const pvalue = linkRelationshipSizes[index]; - linkRelationshipSizes[index] = (pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue + 1); + linkRelationshipSizes[index] = pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue + 1; } //decrement the size of the previous relationship if it already exists (i.e. not default 'link' relationship upon link creation) if (linkRelationshipList.includes(prevRelationship)) { const pindex = linkRelationshipList.indexOf(prevRelationship); if (pindex !== -1 && pindex < linkRelationshipSizes.length) { const pvalue = linkRelationshipSizes[pindex]; - linkRelationshipSizes[pindex] = Math.max(0, (pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue - 1)); + linkRelationshipSizes[pindex] = Math.max(0, pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue - 1); } } - } - this.relationshipButtonColor = "rgb(62, 133, 55)"; - setTimeout(action(() => this.relationshipButtonColor = ""), 750); + this.relationshipButtonColor = 'rgb(62, 133, 55)'; + setTimeout( + action(() => (this.relationshipButtonColor = '')), + 750 + ); return true; } }); @@ -89,144 +94,143 @@ export class LinkEditor extends React.Component<LinkEditorProps> { if (linkRelationshipList) { return linkRelationshipList.filter(rel => rel.includes(query)); } - } + }; /** * toggles visibility of the relationship search results when the input field is focused on */ @action toggleRelationshipResults = () => { - this.relationshipSearchVisibility = this.relationshipSearchVisibility === "none" ? "block" : "none"; - } + this.relationshipSearchVisibility = this.relationshipSearchVisibility === 'none' ? 'block' : 'none'; + }; @undoBatch setDescripValue = action((value: string) => { if (LinkManager.currentLink) { Doc.GetProto(LinkManager.currentLink).description = value; - this.buttonColor = "rgb(62, 133, 55)"; - setTimeout(action(() => this.buttonColor = ""), 750); + this.buttonColor = 'rgb(62, 133, 55)'; + setTimeout( + action(() => (this.buttonColor = '')), + 750 + ); return true; } }); onDescriptionKey = (e: React.KeyboardEvent<HTMLInputElement>) => { - if (e.key === "Enter") { + if (e.key === 'Enter') { this.setDescripValue(this.description); document.getElementById('input')?.blur(); } e.stopPropagation(); - } + }; onRelationshipKey = (e: React.KeyboardEvent<HTMLInputElement>) => { - if (e.key === "Enter") { + if (e.key === 'Enter') { this.setRelationshipValue(this.relationship); document.getElementById('input')?.blur(); } e.stopPropagation(); - } + }; onDescriptionDown = () => this.setDescripValue(this.description); onRelationshipDown = () => this.setRelationshipValue(this.relationship); onBlur = () => { - //only hide the search results if the user clicks out of the input AND not on any of the search results + //only hide the search results if the user clicks out of the input AND not on any of the search results // i.e. if search is not active if (!this.searchIsActive) { this.toggleRelationshipResults(); } - } + }; onFocus = () => { this.toggleRelationshipResults(); - } + }; toggleSearchIsActive = () => { this.searchIsActive = !this.searchIsActive; - } + }; @action - handleDescriptionChange = (e: React.ChangeEvent<HTMLInputElement>) => { this.description = e.target.value; } + handleDescriptionChange = (e: React.ChangeEvent<HTMLInputElement>) => { + this.description = e.target.value; + }; @action - handleRelationshipChange = (e: React.ChangeEvent<HTMLInputElement>) => { this.relationship = e.target.value; } + handleRelationshipChange = (e: React.ChangeEvent<HTMLInputElement>) => { + this.relationship = e.target.value; + }; @action - handleZoomFollowChange = (e: React.ChangeEvent<HTMLInputElement>) => { this.props.sourceDoc.followLinkZoom = e.target.checked; } + handleZoomFollowChange = (e: React.ChangeEvent<HTMLInputElement>) => { + this.props.sourceDoc.followLinkZoom = e.target.checked; + }; @action handleRelationshipSearchChange = (result: string) => { this.setRelationshipValue(result); this.toggleRelationshipResults(); this.relationship = result; - } + }; @computed get editRelationship() { //NOTE: confusingly, the classnames for the following relationship JSX elements are the same as the for the description elements for shared CSS - return <div className="linkEditor-description"> - <div className="linkEditor-description-label">Link Relationship:</div> - <div className="linkEditor-description-input"> - <div className="linkEditor-description-editing"> - <input - style={{ width: "100%" }} - id="input" - value={this.relationship} - autoComplete={"off"} - placeholder={"Enter link relationship"} - onKeyDown={this.onRelationshipKey} - onChange={this.handleRelationshipChange} - onFocus={this.onFocus} - onBlur={this.onBlur} - ></input> - <LinkRelationshipSearch - results={this.getRelationshipResults()} - display={this.relationshipSearchVisibility} - handleRelationshipSearchChange={this.handleRelationshipSearchChange} - toggleSearch={this.toggleSearchIsActive} - /> + return ( + <div className="linkEditor-description"> + <div className="linkEditor-description-label">Link Relationship:</div> + <div className="linkEditor-description-input"> + <div className="linkEditor-description-editing"> + <input + style={{ width: '100%' }} + id="input" + value={this.relationship} + autoComplete={'off'} + placeholder={'Enter link relationship'} + onKeyDown={this.onRelationshipKey} + onChange={this.handleRelationshipChange} + onFocus={this.onFocus} + onBlur={this.onBlur}></input> + <LinkRelationshipSearch results={this.getRelationshipResults()} display={this.relationshipSearchVisibility} handleRelationshipSearchChange={this.handleRelationshipSearchChange} toggleSearch={this.toggleSearchIsActive} /> + </div> + <div className="linkEditor-description-add-button" style={{ background: this.relationshipButtonColor }} onPointerDown={this.onRelationshipDown}> + Set + </div> </div> - <div className="linkEditor-description-add-button" - style={{ background: this.relationshipButtonColor }} - onPointerDown={this.onRelationshipDown}>Set</div> </div> - </div>; + ); } @computed get editZoomFollow() { //NOTE: confusingly, the classnames for the following relationship JSX elements are the same as the for the description elements for shared CSS - return <div className="linkEditor-zoomFollow"> - <div className="linkEditor-zoomFollow-label">Zoom To Link Target:</div> - <div className="linkEditor-zoomFollow-input"> - <div className="linkEditor-zoomFollow-editing"> - <input - style={{ width: "100%" }} - type="checkbox" - value={this.zoomFollow} - onChange={this.handleZoomFollowChange} /> + return ( + <div className="linkEditor-zoomFollow"> + <div className="linkEditor-zoomFollow-label">Zoom To Link Target:</div> + <div className="linkEditor-zoomFollow-input"> + <div className="linkEditor-zoomFollow-editing"> + <input style={{ width: '100%' }} type="checkbox" value={this.zoomFollow} onChange={this.handleZoomFollowChange} /> + </div> </div> </div> - </div >; + ); } @computed get editDescription() { - return <div className="linkEditor-description"> - <div className="linkEditor-description-label">Link Description:</div> - <div className="linkEditor-description-input"> - <div className="linkEditor-description-editing"> - <input - style={{ width: "100%" }} - autoComplete={"off"} - id="input" - value={this.description} - placeholder={"Enter link description"} - onKeyDown={this.onDescriptionKey} - onChange={this.handleDescriptionChange} - ></input> + return ( + <div className="linkEditor-description"> + <div className="linkEditor-description-label">Link Description:</div> + <div className="linkEditor-description-input"> + <div className="linkEditor-description-editing"> + <input style={{ width: '100%' }} autoComplete={'off'} id="input" value={this.description} placeholder={'Enter link description'} onKeyDown={this.onDescriptionKey} onChange={this.handleDescriptionChange}></input> + </div> + <div className="linkEditor-description-add-button" style={{ background: this.buttonColor }} onPointerDown={this.onDescriptionDown}> + Set + </div> </div> - <div className="linkEditor-description-add-button" - style={{ background: this.buttonColor }} - onPointerDown={this.onDescriptionDown}>Set</div> </div> - </div>; + ); } @action - changeDropdown = () => { this.openDropdown = !this.openDropdown; } + changeDropdown = () => { + this.openDropdown = !this.openDropdown; + }; @undoBatch changeFollowBehavior = action((follow: string) => { @@ -236,94 +240,114 @@ export class LinkEditor extends React.Component<LinkEditorProps> { @computed get followingDropdown() { - return <div className="linkEditor-followingDropdown"> - <div className="linkEditor-followingDropdown-label">Follow Behavior:</div> - <div className="linkEditor-followingDropdown-dropdown"> - <div className="linkEditor-followingDropdown-header" - onPointerDown={this.changeDropdown}> - {StrCast(this.props.linkDoc.followLinkLocation, "default")} - <FontAwesomeIcon className="linkEditor-followingDropdown-icon" - icon={this.openDropdown ? "chevron-up" : "chevron-down"} - size={"lg"} /> - </div> - <div className="linkEditor-followingDropdown-optionsList" - style={{ display: this.openDropdown ? "" : "none" }}> - <div className="linkEditor-followingDropdown-option" - onPointerDown={() => this.changeFollowBehavior("default")}> - Default + return ( + <div className="linkEditor-followingDropdown"> + <div className="linkEditor-followingDropdown-label">Follow Behavior:</div> + <div className="linkEditor-followingDropdown-dropdown"> + <div className="linkEditor-followingDropdown-header" onPointerDown={this.changeDropdown}> + {StrCast(this.props.linkDoc.followLinkLocation, 'default')} + <FontAwesomeIcon className="linkEditor-followingDropdown-icon" icon={this.openDropdown ? 'chevron-up' : 'chevron-down'} size={'lg'} /> </div> - <div className="linkEditor-followingDropdown-option" - onPointerDown={() => this.changeFollowBehavior("add:left")}> - Always open in new left pane - </div> - <div className="linkEditor-followingDropdown-option" - onPointerDown={() => this.changeFollowBehavior("add:right")}> - Always open in new right pane - </div> - <div className="linkEditor-followingDropdown-option" - onPointerDown={() => this.changeFollowBehavior("replace:right")}> - Always replace right tab - </div> - <div className="linkEditor-followingDropdown-option" - onPointerDown={() => this.changeFollowBehavior("replace:left")}> - Always replace left tab - </div> - <div className="linkEditor-followingDropdown-option" - onPointerDown={() => this.changeFollowBehavior("fullScreen")}> - Always open full screen - </div> - <div className="linkEditor-followingDropdown-option" - onPointerDown={() => this.changeFollowBehavior("add")}> - Always open in a new tab - </div> - <div className="linkEditor-followingDropdown-option" - onPointerDown={() => this.changeFollowBehavior("replace")}> - Replace Tab - </div> - {this.props.linkDoc.linksToAnnotation ? - <div className="linkEditor-followingDropdown-option" - onPointerDown={() => this.changeFollowBehavior("openExternal")}> - Always open in external page + <div className="linkEditor-followingDropdown-optionsList" style={{ display: this.openDropdown ? '' : 'none' }}> + <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('default')}> + Default + </div> + <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('add:left')}> + Always open in new left pane + </div> + <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('add:right')}> + Always open in new right pane + </div> + <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('replace:right')}> + Always replace right tab + </div> + <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('replace:left')}> + Always replace left tab </div> - : null} + <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('fullScreen')}> + Always open full screen + </div> + <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('add')}> + Always open in a new tab + </div> + <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('replace')}> + Replace Tab + </div> + {this.props.linkDoc.linksToAnnotation ? ( + <div className="linkEditor-followingDropdown-option" onPointerDown={() => this.changeFollowBehavior('openExternal')}> + Always open in external page + </div> + ) : null} + </div> </div> </div> - </div>; + ); } @action - changeInfo = () => { this.showInfo = !this.showInfo; } + changeInfo = () => { + this.showInfo = !this.showInfo; + }; render() { const destination = LinkManager.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc); - return !destination ? (null) : ( + return !destination ? null : ( <div className="linkEditor" tabIndex={0} onKeyDown={e => e.stopPropagation()}> <div className="linkEditor-info"> - <Tooltip title={<><div className="dash-tooltip">Return to link menu</div></>} placement="top"> - <button className="linkEditor-button-back" - style={{ display: this.props.hideback ? "none" : "" }} - onClick={this.props.showLinks}> - <FontAwesomeIcon icon="arrow-left" size="sm" /> </button> + <Tooltip + title={ + <> + <div className="dash-tooltip">Return to link menu</div> + </> + } + placement="top"> + <button className="linkEditor-button-back" style={{ display: this.props.hideback ? 'none' : '' }} onClick={this.props.showLinks}> + <FontAwesomeIcon icon="arrow-left" size="sm" />{' '} + </button> </Tooltip> - <p className="linkEditor-linkedTo">Editing Link to: <b>{ - destination.proto?.title ?? destination.title ?? "untitled"}</b></p> - <Tooltip title={<><div className="dash-tooltip">Show more link information</div></>} placement="top"> - <div className="linkEditor-downArrow"><FontAwesomeIcon className="button" icon={this.infoIcon} size="lg" onPointerDown={this.changeInfo} /></div> + <p className="linkEditor-linkedTo"> + Editing Link to: <b>{StrCast(destination.proto?.title, StrCast(destination.title, 'untitled'))}</b> + </p> + <Tooltip + title={ + <> + <div className="dash-tooltip">Show more link information</div> + </> + } + placement="top"> + <div className="linkEditor-downArrow"> + <FontAwesomeIcon className="button" icon={this.infoIcon} size="lg" onPointerDown={this.changeInfo} /> + </div> </Tooltip> </div> - {this.showInfo ? <div className="linkEditor-moreInfo"> - <div>{this.props.linkDoc.author ? <div> <b>Author:</b> {this.props.linkDoc.author}</div> : null}</div> - <div>{this.props.linkDoc.creationDate ? <div> <b>Creation Date:</b> - {DateCast(this.props.linkDoc.creationDate).toString()}</div> : null}</div> - </div> : null} + {this.showInfo ? ( + <div className="linkEditor-moreInfo"> + <div> + {this.props.linkDoc.author ? ( + <div> + {' '} + <b>Author:</b> {StrCast(this.props.linkDoc.author)} + </div> + ) : null} + </div> + <div> + {this.props.linkDoc.creationDate ? ( + <div> + {' '} + <b>Creation Date:</b> + {DateCast(this.props.linkDoc.creationDate).toString()} + </div> + ) : null} + </div> + </div> + ) : null} {this.editDescription} {this.editRelationship} {this.editZoomFollow} {this.followingDropdown} </div> - ); } -}
\ No newline at end of file +} diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index a75e7a0c4..1e7f4f10b 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -2,7 +2,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; import { action, observable } from 'mobx'; -import { observer } from "mobx-react"; +import { observer } from 'mobx-react'; import { Doc, DocListCast } from '../../../fields/Doc'; import { Cast, StrCast } from '../../../fields/Types'; import { WebField } from '../../../fields/URLField'; @@ -16,8 +16,7 @@ import { undoBatch } from '../../util/UndoManager'; import { DocumentView } from '../nodes/DocumentView'; import { LinkDocPreview } from '../nodes/LinkDocPreview'; import './LinkMenuItem.scss'; -import React = require("react"); - +import React = require('react'); interface LinkMenuItemProps { groupType: string; @@ -50,22 +49,21 @@ export async function StartLinkTargetsDrag(dragEle: HTMLElement, docView: Docume }; const containingView = docView.props.ContainingCollectionView; const finishDrag = (e: DragManager.DragCompleteEvent) => - e.docDragData && (e.docDragData.droppedDocuments = - dragData.draggedDocuments.reduce((droppedDocs, d) => { - const dvs = DocumentManager.Instance.getDocumentViews(d).filter(dv => dv.props.ContainingCollectionView === containingView); - if (dvs.length) { - dvs.forEach(dv => droppedDocs.push(dv.props.Document)); - } else { - droppedDocs.push(Doc.MakeAlias(d)); - } - return droppedDocs; - }, [] as Doc[])); + e.docDragData && + (e.docDragData.droppedDocuments = dragData.draggedDocuments.reduce((droppedDocs, d) => { + const dvs = DocumentManager.Instance.getDocumentViews(d).filter(dv => dv.props.ContainingCollectionView === containingView); + if (dvs.length) { + dvs.forEach(dv => droppedDocs.push(dv.props.Document)); + } else { + droppedDocs.push(Doc.MakeAlias(d)); + } + return droppedDocs; + }, [] as Doc[])); DragManager.StartDrag([dragEle], dragData, downX, downY, undefined, finishDrag); } } - @observer export class LinkMenuItem extends React.Component<LinkMenuItemProps> { private _drag = React.createRef<HTMLDivElement>(); @@ -74,20 +72,31 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> { _buttonRef = React.createRef<HTMLDivElement>(); @observable private _showMore: boolean = false; - @action toggleShowMore(e: React.PointerEvent) { e.stopPropagation(); this._showMore = !this._showMore; } + @action toggleShowMore(e: React.PointerEvent) { + e.stopPropagation(); + this._showMore = !this._showMore; + } onEdit = (e: React.PointerEvent): void => { LinkManager.currentLink = this.props.linkDoc; - setupMoveUpEvents(this, e, e => { - const dragData = new DragManager.DocumentDragData([this.props.linkDoc], "alias"); - dragData.removeDropProperties = ["hidden"]; - DragManager.StartDocumentDrag([this._editRef.current!], dragData, e.x, e.y); - return true; - }, emptyFunction, () => this.props.showEditor(this.props.linkDoc)); - } + setupMoveUpEvents( + this, + e, + e => { + const dragData = new DragManager.DocumentDragData([this.props.linkDoc], 'alias'); + dragData.removeDropProperties = ['hidden']; + DragManager.StartDocumentDrag([this._editRef.current!], dragData, e.x, e.y); + return true; + }, + emptyFunction, + () => this.props.showEditor(this.props.linkDoc) + ); + }; onLinkButtonDown = (e: React.PointerEvent): void => { - setupMoveUpEvents(this, e, + setupMoveUpEvents( + this, + e, e => { const eleClone: any = this._drag.current!.cloneNode(true); eleClone.style.transform = `translate(${e.x}px, ${e.y}px)`; @@ -99,109 +108,170 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> { () => { this.props.clearLinkEditor(); if (this.props.itemHandler) { - this.props.itemHandler?.(this.props.linkDoc); } else { - const focusDoc = Cast(this.props.linkDoc.anchor1, Doc, null)?.annotationOn === this.props.sourceDoc ? Cast(this.props.linkDoc.anchor1, Doc, null) : - Cast(this.props.linkDoc.anchor2, Doc, null)?.annotationOn === this.props.sourceDoc ? Cast(this.props.linkDoc.anchor12, Doc, null) : undefined; + const focusDoc = + Cast(this.props.linkDoc.anchor1, Doc, null)?.annotationOn === this.props.sourceDoc + ? Cast(this.props.linkDoc.anchor1, Doc, null) + : Cast(this.props.linkDoc.anchor2, Doc, null)?.annotationOn === this.props.sourceDoc + ? Cast(this.props.linkDoc.anchor12, Doc, null) + : undefined; if (focusDoc) this.props.docView.ComponentView?.scrollFocus?.(focusDoc, true); LinkManager.FollowLink(this.props.linkDoc, this.props.sourceDoc, this.props.docView.props, false); } - }); - } + } + ); + }; deleteLink = (e: React.PointerEvent): void => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => { - this.props.linkDoc.linksToAnnotation && Hypothesis.deleteLink(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc); - LinkManager.Instance.deleteLink(this.props.linkDoc); - }))); - } + setupMoveUpEvents( + this, + e, + returnFalse, + emptyFunction, + undoBatch( + action(() => { + this.props.linkDoc.linksToAnnotation && Hypothesis.deleteLink(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc); + LinkManager.Instance.deleteLink(this.props.linkDoc); + }) + ) + ); + }; autoMove = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.props.linkDoc.linkAutoMove = !this.props.linkDoc.linkAutoMove))); - } + setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => (this.props.linkDoc.linkAutoMove = !this.props.linkDoc.linkAutoMove)))); + }; showLink = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.props.linkDoc.linkDisplay = !this.props.linkDoc.linkDisplay))); - } + setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => (this.props.linkDoc.linkDisplay = !this.props.linkDoc.linkDisplay)))); + }; showAnchor = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.props.linkDoc.hidden = !this.props.linkDoc.hidden))); - } + setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => (this.props.linkDoc.hidden = !this.props.linkDoc.hidden)))); + }; render() { const destinationIcon = Doc.toIcon(this.props.destinationDoc) as any as IconProp; - const title = StrCast(this.props.destinationDoc.title).length > 18 ? - StrCast(this.props.destinationDoc.title).substr(0, 14) + "..." : this.props.destinationDoc.title; + const title = StrCast(this.props.destinationDoc.title).length > 18 ? StrCast(this.props.destinationDoc.title).substr(0, 14) + '...' : this.props.destinationDoc.title; // ... // from anika to bob: here's where the text that is specifically linked would show up (linkDoc.storedText) // ... - const source = this.props.sourceDoc.type === DocumentType.RTF ? this.props.linkDoc.storedText ? - StrCast(this.props.linkDoc.storedText).length > 17 ? - StrCast(this.props.linkDoc.storedText).substr(0, 18) - : this.props.linkDoc.storedText : undefined : undefined; + const source = + this.props.sourceDoc.type === DocumentType.RTF + ? this.props.linkDoc.storedText + ? StrCast(this.props.linkDoc.storedText).length > 17 + ? StrCast(this.props.linkDoc.storedText).substr(0, 18) + : this.props.linkDoc.storedText + : undefined + : undefined; return ( <div className="linkMenu-item"> - <div className={"linkMenu-item-content expand-two"}> - - <div ref={this._drag} className="linkMenu-name" //title="drag to view target. click to customize." + <div className={'linkMenu-item-content expand-two'}> + <div + ref={this._drag} + className="linkMenu-name" //title="drag to view target. click to customize." onPointerLeave={LinkDocPreview.Clear} - onPointerEnter={e => this.props.linkDoc && LinkDocPreview.SetLinkInfo({ - docProps: this.props.docView.props, - linkSrc: this.props.sourceDoc, - linkDoc: this.props.linkDoc, - showHeader: false, - location: [e.clientX, e.clientY + 20] - })} + onPointerEnter={e => + this.props.linkDoc && + LinkDocPreview.SetLinkInfo({ + docProps: this.props.docView.props, + linkSrc: this.props.sourceDoc, + linkDoc: this.props.linkDoc, + showHeader: false, + location: [e.clientX, e.clientY + 20], + }) + } onPointerDown={this.onLinkButtonDown}> - <div className="linkMenu-text"> - {source ? <p className="linkMenu-source-title"> <b>Source: {source}</b></p> : null} + {source ? ( + <p className="linkMenu-source-title"> + {' '} + <b>Source: {StrCast(source)}</b> + </p> + ) : null} <div className="linkMenu-title-wrapper"> - <div className="destination-icon-wrapper" > + <div className="destination-icon-wrapper"> <FontAwesomeIcon className="destination-icon" icon={destinationIcon} size="sm" /> </div> <p className="linkMenu-destination-title"> - {this.props.linkDoc.linksToAnnotation && Cast(this.props.destinationDoc.data, WebField)?.url.href === this.props.linkDoc.annotationUri ? "Annotation in" : ""} {title} + {this.props.linkDoc.linksToAnnotation && Cast(this.props.destinationDoc.data, WebField)?.url.href === this.props.linkDoc.annotationUri ? 'Annotation in' : ''} {StrCast(title)} </p> </div> - {!this.props.linkDoc.description ? (null) : <p className="linkMenu-description">{StrCast(this.props.linkDoc.description)}</p>} + {!this.props.linkDoc.description ? null : <p className="linkMenu-description">{StrCast(this.props.linkDoc.description)}</p>} </div> - <div className="linkMenu-item-buttons" ref={this._buttonRef} > - - <Tooltip title={<><div className="dash-tooltip">{this.props.linkDoc.hidden ? "Show Link Anchor" : "Hide Link Anchor"}</div></>}> - <div className="button" ref={this._editRef} style={{ background: this.props.linkDoc.hidden ? "" : "#4476f7" /* $medium-blue */ }} onPointerDown={this.showAnchor} onClick={e => e.stopPropagation()}> - <FontAwesomeIcon className="fa-icon" icon={"eye"} size="sm" /></div> + <div className="linkMenu-item-buttons" ref={this._buttonRef}> + <Tooltip + title={ + <> + <div className="dash-tooltip">{this.props.linkDoc.hidden ? 'Show Link Anchor' : 'Hide Link Anchor'}</div> + </> + }> + <div className="button" ref={this._editRef} style={{ background: this.props.linkDoc.hidden ? '' : '#4476f7' /* $medium-blue */ }} onPointerDown={this.showAnchor} onClick={e => e.stopPropagation()}> + <FontAwesomeIcon className="fa-icon" icon={'eye'} size="sm" /> + </div> </Tooltip> - <Tooltip title={<><div className="dash-tooltip">{this.props.linkDoc.linkDisplay ? "Hide Link Line" : "Show Link Line"}</div></>}> - <div className="button" ref={this._editRef} style={{ background: this.props.linkDoc.hidden ? "gray" : this.props.linkDoc.linkDisplay ? "#4476f7"/* $medium-blue */ : "" }} onPointerDown={this.showLink} onClick={e => e.stopPropagation()}> - <FontAwesomeIcon className="fa-icon" icon={"project-diagram"} size="sm" /></div> + <Tooltip + title={ + <> + <div className="dash-tooltip">{this.props.linkDoc.linkDisplay ? 'Hide Link Line' : 'Show Link Line'}</div> + </> + }> + <div + className="button" + ref={this._editRef} + style={{ background: this.props.linkDoc.hidden ? 'gray' : this.props.linkDoc.linkDisplay ? '#4476f7' /* $medium-blue */ : '' }} + onPointerDown={this.showLink} + onClick={e => e.stopPropagation()}> + <FontAwesomeIcon className="fa-icon" icon={'project-diagram'} size="sm" /> + </div> </Tooltip> - <Tooltip title={<><div className="dash-tooltip">{this.props.linkDoc.linkAutoMove ? "Click to freeze link anchor position" : "Click to auto move link anchor"}</div></>}> - <div className="button" ref={this._editRef} style={{ background: this.props.linkDoc.hidden ? "gray" : !this.props.linkDoc.linkAutoMove ? "" : "#4476f7" /* $medium-blue */ }} onPointerDown={this.autoMove} onClick={e => e.stopPropagation()}> - <FontAwesomeIcon className="fa-icon" icon={"play"} size="sm" /></div> + <Tooltip + title={ + <> + <div className="dash-tooltip">{this.props.linkDoc.linkAutoMove ? 'Click to freeze link anchor position' : 'Click to auto move link anchor'}</div> + </> + }> + <div + className="button" + ref={this._editRef} + style={{ background: this.props.linkDoc.hidden ? 'gray' : !this.props.linkDoc.linkAutoMove ? '' : '#4476f7' /* $medium-blue */ }} + onPointerDown={this.autoMove} + onClick={e => e.stopPropagation()}> + <FontAwesomeIcon className="fa-icon" icon={'play'} size="sm" /> + </div> </Tooltip> - <Tooltip title={<><div className="dash-tooltip">Edit Link</div></>}> + <Tooltip + title={ + <> + <div className="dash-tooltip">Edit Link</div> + </> + }> <div className="button" ref={this._editRef} onPointerDown={this.onEdit} onClick={e => e.stopPropagation()}> - <FontAwesomeIcon className="fa-icon" icon="edit" size="sm" /></div> + <FontAwesomeIcon className="fa-icon" icon="edit" size="sm" /> + </div> </Tooltip> - <Tooltip title={<><div className="dash-tooltip">Delete Link</div></>}> + <Tooltip + title={ + <> + <div className="dash-tooltip">Delete Link</div> + </> + }> <div className="button" onPointerDown={this.deleteLink} onClick={e => e.stopPropagation()}> - <FontAwesomeIcon className="fa-icon" icon="trash" size="sm" /></div> + <FontAwesomeIcon className="fa-icon" icon="trash" size="sm" /> + </div> </Tooltip> </div> </div> </div> - - </div > + </div> ); } -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 96ac3e332..f1d8123da 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -1,48 +1,48 @@ -import { computed } from "mobx"; -import { observer } from "mobx-react"; -import { AclPrivate, Doc, Opt } from "../../../fields/Doc"; -import { ScriptField } from "../../../fields/ScriptField"; -import { Cast, StrCast } from "../../../fields/Types"; -import { GetEffectiveAcl, TraceMobx } from "../../../fields/util"; -import { emptyPath, OmitKeys, Without } from "../../../Utils"; -import { DirectoryImportBox } from "../../util/Import & Export/DirectoryImportBox"; -import { CollectionDockingView } from "../collections/CollectionDockingView"; -import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; -import { CollectionSchemaView } from "../collections/collectionSchema/CollectionSchemaView"; -import { CollectionView } from "../collections/CollectionView"; -import { InkingStroke } from "../InkingStroke"; -import { PresElementBox } from "../nodes/trails/PresElementBox"; -import { SearchBox } from "../search/SearchBox"; -import { DashWebRTCVideo } from "../webcam/DashWebRTCVideo"; -import { YoutubeBox } from "./../../apis/youtube/YoutubeBox"; -import { AudioBox } from "./AudioBox"; -import { FontIconBox } from "./button/FontIconBox"; -import { ColorBox } from "./ColorBox"; -import { ComparisonBox } from "./ComparisonBox"; -import { DataVizBox } from "./DataVizBox/DataVizBox"; -import { DocumentViewProps } from "./DocumentView"; -import "./DocumentView.scss"; -import { EquationBox } from "./EquationBox"; -import { FieldView, FieldViewProps } from "./FieldView"; -import { FilterBox } from "./FilterBox"; -import { FormattedTextBox, FormattedTextBoxProps } from "./formattedText/FormattedTextBox"; -import { FunctionPlotBox } from "./FunctionPlotBox"; -import { ImageBox } from "./ImageBox"; -import { KeyValueBox } from "./KeyValueBox"; -import { LabelBox } from "./LabelBox"; -import { LinkAnchorBox } from "./LinkAnchorBox"; -import { LinkBox } from "./LinkBox"; -import { MapBox } from "./MapBox/MapBox"; -import { PDFBox } from "./PDFBox"; -import { RecordingBox } from "./RecordingBox"; -import { ScreenshotBox } from "./ScreenshotBox"; -import { ScriptingBox } from "./ScriptingBox"; -import { SliderBox } from "./SliderBox"; -import { PresBox } from "./trails/PresBox"; -import { VideoBox } from "./VideoBox"; -import { WebBox } from "./WebBox"; -import React = require("react"); -import XRegExp = require("xregexp"); +import { computed } from 'mobx'; +import { observer } from 'mobx-react'; +import { AclPrivate, Doc, Opt } from '../../../fields/Doc'; +import { ScriptField } from '../../../fields/ScriptField'; +import { Cast, StrCast } from '../../../fields/Types'; +import { GetEffectiveAcl, TraceMobx } from '../../../fields/util'; +import { emptyPath, OmitKeys, Without } from '../../../Utils'; +import { DirectoryImportBox } from '../../util/Import & Export/DirectoryImportBox'; +import { CollectionDockingView } from '../collections/CollectionDockingView'; +import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; +import { CollectionSchemaView } from '../collections/collectionSchema/CollectionSchemaView'; +import { CollectionView } from '../collections/CollectionView'; +import { InkingStroke } from '../InkingStroke'; +import { PresElementBox } from '../nodes/trails/PresElementBox'; +import { SearchBox } from '../search/SearchBox'; +import { DashWebRTCVideo } from '../webcam/DashWebRTCVideo'; +import { YoutubeBox } from './../../apis/youtube/YoutubeBox'; +import { AudioBox } from './AudioBox'; +import { FontIconBox } from './button/FontIconBox'; +import { ColorBox } from './ColorBox'; +import { ComparisonBox } from './ComparisonBox'; +import { DataVizBox } from './DataVizBox/DataVizBox'; +import { DocumentViewProps } from './DocumentView'; +import './DocumentView.scss'; +import { EquationBox } from './EquationBox'; +import { FieldView, FieldViewProps } from './FieldView'; +import { FilterBox } from './FilterBox'; +import { FormattedTextBox, FormattedTextBoxProps } from './formattedText/FormattedTextBox'; +import { FunctionPlotBox } from './FunctionPlotBox'; +import { ImageBox } from './ImageBox'; +import { KeyValueBox } from './KeyValueBox'; +import { LabelBox } from './LabelBox'; +import { LinkAnchorBox } from './LinkAnchorBox'; +import { LinkBox } from './LinkBox'; +import { MapBox } from './MapBox/MapBox'; +import { PDFBox } from './PDFBox'; +import { RecordingBox } from './RecordingBox'; +import { ScreenshotBox } from './ScreenshotBox'; +import { ScriptingBox } from './ScriptingBox'; +import { SliderBox } from './SliderBox'; +import { PresBox } from './trails/PresBox'; +import { VideoBox } from './VideoBox'; +import { WebBox } from './WebBox'; +import React = require('react'); +import XRegExp = require('xregexp'); const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? @@ -60,7 +60,6 @@ class ObserverJsxParser1 extends JsxParser { const ObserverJsxParser: typeof JsxParser = ObserverJsxParser1 as any; - interface HTMLtagProps { Document: Doc; RootDoc: Doc; @@ -68,16 +67,17 @@ interface HTMLtagProps { onClick?: ScriptField; onInput?: ScriptField; scaling: number; + children?: JSX.Element[]; } //"<HTMLdiv borderRadius='100px' onClick={this.bannerColor=this.bannerColor==='red'?'green':'red'} overflow='hidden' position='absolute' width='100%' height='100%' transform='rotate({2*this.x+this.y}deg)'> <ImageBox {...props} fieldKey={'data'}/> <HTMLspan width='200px' top='0' height='35px' textAlign='center' paddingTop='10px' transform='translate(-40px, 45px) rotate(-45deg)' position='absolute' color='{this.bannerColor===`green`?`light`:`dark`}blue' backgroundColor='{this.bannerColor===`green`?`dark`:`light`}blue'> {this.title}</HTMLspan></HTMLdiv>" -//"<HTMLdiv borderRadius='100px' overflow='hidden' position='absolute' width='100%' height='100%' -// transform='rotate({2*this.x+this.y}deg)' +//"<HTMLdiv borderRadius='100px' overflow='hidden' position='absolute' width='100%' height='100%' +// transform='rotate({2*this.x+this.y}deg)' // onClick = { this.bannerColor = this.bannerColor === 'red' ? 'green' : 'red' } > // <ImageBox {...props} fieldKey={'data'}/> -// <HTMLspan width='200px' top='0' height='35px' textAlign='center' paddingTop='10px' -// transform='translate(-40px, 45px) rotate(-45deg)' position='absolute' -// color='{this.bannerColor===`green`?`light`:`dark`}blue' +// <HTMLspan width='200px' top='0' height='35px' textAlign='center' paddingTop='10px' +// transform='translate(-40px, 45px) rotate(-45deg)' position='absolute' +// color='{this.bannerColor===`green`?`light`:`dark`}blue' // backgroundColor='{this.bannerColor===`green`?`dark`:`light`}blue'> // {this.title} // </HTMLspan> @@ -87,45 +87,51 @@ export class HTMLtag extends React.Component<HTMLtagProps> { click = (e: React.MouseEvent) => { const clickScript = (this.props as any).onClick as Opt<ScriptField>; clickScript?.script.run({ this: this.props.Document, self: this.props.RootDoc, scale: this.props.scaling }); - } + }; onInput = (e: React.FormEvent<HTMLDivElement>) => { const onInputScript = (this.props as any).onInput as Opt<ScriptField>; onInputScript?.script.run({ this: this.props.Document, self: this.props.RootDoc, value: (e.target as any).textContent }); - } + }; render() { const style: { [key: string]: any } = {}; - const divKeys = OmitKeys(this.props, ["children", "htmltag", "RootDoc", "scaling", "Document", "key", "onInput", "onClick", "__proto__"]).omit; - const replacer = (match: any, expr: string, offset: any, string: any) => { // bcz: this executes a script to convert a propery expression string: { script } into a value - return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: "number" })?.script.run({ self: this.props.RootDoc, this: this.props.Document, scale: this.props.scaling }).result as string || ""; + const divKeys = OmitKeys(this.props, ['children', 'htmltag', 'RootDoc', 'scaling', 'Document', 'key', 'onInput', 'onClick', '__proto__']).omit; + const replacer = (match: any, expr: string, offset: any, string: any) => { + // bcz: this executes a script to convert a propery expression string: { script } into a value + return (ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ self: this.props.RootDoc, this: this.props.Document, scale: this.props.scaling }).result as string) || ''; }; Object.keys(divKeys).map((prop: string) => { const p = (this.props as any)[prop] as string; style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer); }); const Tag = this.props.htmltag as keyof JSX.IntrinsicElements; - return <Tag style={style} onClick={this.click} onInput={this.onInput as any}> - {this.props.children} - </Tag>; + return ( + <Tag style={style} onClick={this.click} onInput={this.onInput as any}> + {this.props.children} + </Tag> + ); } } @observer -export class DocumentContentsView extends React.Component<DocumentViewProps & FormattedTextBoxProps & { - isSelected: (outsideReaction: boolean) => boolean, - select: (ctrl: boolean) => void, - scaling?: () => number, - setHeight?: (height: number) => void, - layoutKey: string, -}> { +export class DocumentContentsView extends React.Component< + DocumentViewProps & + FormattedTextBoxProps & { + isSelected: (outsideReaction: boolean) => boolean; + select: (ctrl: boolean) => void; + scaling?: () => number; + setHeight?: (height: number) => void; + layoutKey: string; + } +> { @computed get layout(): string { TraceMobx(); if (this.props.LayoutTemplateString) return this.props.LayoutTemplateString; - if (!this.layoutDoc) return "<p>awaiting layout</p>"; - if (this.props.layoutKey === "layout_keyValue") return StrCast(this.props.Document.layout_keyValue, KeyValueBox.LayoutString("data")); - const layout = Cast(this.layoutDoc[this.layoutDoc === this.props.Document && this.props.layoutKey ? this.props.layoutKey : StrCast(this.layoutDoc.layoutKey, "layout")], "string"); - if (layout === undefined) return this.props.Document.data ? "<FieldView {...props} fieldKey='data' />" : KeyValueBox.LayoutString(this.layoutDoc.proto ? "proto" : ""); - if (typeof layout === "string") return layout; - return "<p>Loading layout</p>"; + if (!this.layoutDoc) return '<p>awaiting layout</p>'; + if (this.props.layoutKey === 'layout_keyValue') return StrCast(this.props.Document.layout_keyValue, KeyValueBox.LayoutString('data')); + const layout = Cast(this.layoutDoc[this.layoutDoc === this.props.Document && this.props.layoutKey ? this.props.layoutKey : StrCast(this.layoutDoc.layoutKey, 'layout')], 'string'); + if (layout === undefined) return this.props.Document.data ? "<FieldView {...props} fieldKey='data' />" : KeyValueBox.LayoutString(this.layoutDoc.proto ? 'proto' : ''); + if (typeof layout === 'string') return layout; + return '<p>Loading layout</p>'; } get dataDoc() { @@ -136,36 +142,39 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo const params = StrCast(this.props.Document.PARAMS); // bcz: replaced this with below : is it correct? change was made to accommodate passing fieldKey's from a layout script // const template: Doc = this.props.LayoutTemplate?.() || Doc.Layout(this.props.Document, this.props.layoutKey ? Cast(this.props.Document[this.props.layoutKey], Doc, null) : undefined); - const template: Doc = this.props.LayoutTemplate?.() || (this.props.LayoutTemplateString && this.props.Document) || + const template: Doc = + this.props.LayoutTemplate?.() || + (this.props.LayoutTemplateString && this.props.Document) || (this.props.layoutKey && StrCast(this.props.Document[this.props.layoutKey]) && this.props.Document) || Doc.Layout(this.props.Document, this.props.layoutKey ? Cast(this.props.Document[this.props.layoutKey], Doc, null) : undefined); - return Doc.expandTemplateLayout(template, this.props.Document, params ? "(" + params + ")" : this.props.layoutKey); + return Doc.expandTemplateLayout(template, this.props.Document, params ? '(' + params + ')' : this.props.layoutKey); } CreateBindings(onClick: Opt<ScriptField>, onInput: Opt<ScriptField>): JsxBindings { - const docOnlyProps = [ // these are the properties in DocumentViewProps that need to be removed to pass on only DocumentSharedViewProps to the FieldViews - "hideResizeHandles", - "hideTitle", - "treeViewDoc", - "contentPointerEvents", - "radialMenu", - "LayoutTemplateString", - "LayoutTemplate", - "dontCenter", - "ContentScaling", - "contextMenuItems", - "onClick", - "onDoubleClick", - "onPointerDown", - "onPointerUp", + const docOnlyProps = [ + // these are the properties in DocumentViewProps that need to be removed to pass on only DocumentSharedViewProps to the FieldViews + 'hideResizeHandles', + 'hideTitle', + 'treeViewDoc', + 'contentPointerEvents', + 'radialMenu', + 'LayoutTemplateString', + 'LayoutTemplate', + 'dontCenter', + 'ContentScaling', + 'contextMenuItems', + 'onClick', + 'onDoubleClick', + 'onPointerDown', + 'onPointerUp', ]; const list = { - ...OmitKeys(this.props, [...docOnlyProps], "").omit, + ...OmitKeys(this.props, [...docOnlyProps], '').omit, RootDoc: Cast(this.layoutDoc?.rootDocument, Doc, null) || this.layoutDoc, Document: this.layoutDoc, DataDoc: this.dataDoc, onClick: onClick, - onInput: onInput + onInput: onInput, }; return { props: list }; } @@ -180,17 +189,17 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo // replace code content with a script >{content}< as in <HTMLdiv>{this.title}</HTMLdiv> const replacer = (match: any, prefix: string, expr: string, postfix: string, offset: any, string: any) => { - return prefix + (ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name })?.script.run({ this: this.props.Document }).result as string || "") + postfix; + return prefix + ((ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name })?.script.run({ this: this.props.Document }).result as string) || '') + postfix; }; layoutFrame = layoutFrame.replace(/(>[^{]*)[^=]\{([^.'][^<}]+)\}([^}]*<)/g, replacer); - // replace HTML<tag> with corresponding HTML tag as in: <HTMLdiv> becomes <HTMLtag Document={props.Document} htmltag='div'> + // replace HTML<tag> with corresponding HTML tag as in: <HTMLdiv> becomes <HTMLtag Document={props.Document} htmltag='div'> const replacer2 = (match: any, p1: string, offset: any, string: any) => { return `<HTMLtag RootDoc={props.RootDoc} Document={props.Document} scaling='${this.props.scaling?.() || 1}' htmltag='${p1}'`; }; layoutFrame = layoutFrame.replace(/<HTML([a-zA-Z0-9_-]+)/g, replacer2); - // replace /HTML<tag> with </HTMLdiv> as in: </HTMLdiv> becomes </HTMLtag> + // replace /HTML<tag> with </HTMLdiv> as in: </HTMLdiv> becomes </HTMLtag> const replacer3 = (match: any, p1: string, offset: any, string: any) => { return `</HTMLtag`; }; @@ -200,16 +209,16 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo const makeFuncProp = (func: string) => { const splits = layoutFrame.split(`${func}=`); if (splits.length > 1) { - const code = XRegExp.matchRecursive(splits[1], "{", "}", "", { valueNames: ["between", "left", "match", "right", "between"] }); + const code = XRegExp.matchRecursive(splits[1], '{', '}', '', { valueNames: ['between', 'left', 'match', 'right', 'between'] }); layoutFrame = splits[0] + ` ${func}={props.${func}} ` + splits[1].substring(code[1].end + 1); - const script = code[1].value.replace(/^‘/, "").replace(/’$/, ""); // ‘’ are not valid quotes in javascript so get rid of them -- they may be present to make it easier to write complex scripts - see headerTemplate in currentUserUtils.ts - return ScriptField.MakeScript(script, { this: Doc.name, self: Doc.name, scale: "number", value: "string" }); + const script = code[1].value.replace(/^‘/, '').replace(/’$/, ''); // ‘’ are not valid quotes in javascript so get rid of them -- they may be present to make it easier to write complex scripts - see headerTemplate in currentUserUtils.ts + return ScriptField.MakeScript(script, { this: Doc.name, self: Doc.name, scale: 'number', value: 'string' }); } return undefined; // add input function to props }; - const onClick = makeFuncProp("onClick"); - const onInput = makeFuncProp("onInput"); + const onClick = makeFuncProp('onClick'); + const onInput = makeFuncProp('onInput'); const bindings = this.CreateBindings(onClick, onInput); return { bindings, layoutFrame }; } @@ -218,23 +227,55 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo TraceMobx(); const { bindings, layoutFrame } = this.renderData; - return (this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || GetEffectiveAcl(this.layoutDoc) === AclPrivate) ? (null) : + return this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || GetEffectiveAcl(this.layoutDoc) === AclPrivate ? null : ( <ObserverJsxParser key={42} blacklistedAttrs={emptyPath} renderInWrapper={false} components={{ - FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, LabelBox, EquationBox, SliderBox, FieldView, - CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox, - PDFBox, VideoBox, AudioBox, RecordingBox, PresBox, YoutubeBox, PresElementBox, SearchBox, FilterBox, FunctionPlotBox, - ColorBox, DashWebRTCVideo, LinkAnchorBox, InkingStroke, LinkBox, ScriptingBox, MapBox, - ScreenshotBox, DataVizBox, HTMLtag, ComparisonBox + FormattedTextBox, + ImageBox, + DirectoryImportBox, + FontIconBox, + LabelBox, + EquationBox, + SliderBox, + FieldView, + CollectionFreeFormView, + CollectionDockingView, + CollectionSchemaView, + CollectionView, + WebBox, + KeyValueBox, + PDFBox, + VideoBox, + AudioBox, + RecordingBox, + PresBox, + YoutubeBox, + PresElementBox, + SearchBox, + FilterBox, + FunctionPlotBox, + ColorBox, + DashWebRTCVideo, + LinkAnchorBox, + InkingStroke, + LinkBox, + ScriptingBox, + MapBox, + ScreenshotBox, + DataVizBox, + HTMLtag, + ComparisonBox, }} bindings={bindings} jsx={layoutFrame} showWarnings={true} - - onError={(test: any) => { console.log("DocumentContentsView:" + test); }} - />; + onError={(test: any) => { + console.log('DocumentContentsView:' + test); + }} + /> + ); } -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index 6c7e174f7..47d5c662e 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -1,19 +1,19 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; import { action, computed, observable } from 'mobx'; -import { observer } from "mobx-react"; -import wiki from "wikijs"; -import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocCastAsync } from "../../../fields/Doc"; -import { NumCast, StrCast, Cast } from "../../../fields/Types"; -import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, setupMoveUpEvents, Utils } from "../../../Utils"; +import { observer } from 'mobx-react'; +import wiki from 'wikijs'; +import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocCastAsync } from '../../../fields/Doc'; +import { NumCast, StrCast, Cast } from '../../../fields/Types'; +import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, setupMoveUpEvents, Utils } from '../../../Utils'; import { DocServer } from '../../DocServer'; -import { Docs, DocUtils } from "../../documents/Documents"; +import { Docs, DocUtils } from '../../documents/Documents'; import { LinkManager } from '../../util/LinkManager'; -import { Transform } from "../../util/Transform"; +import { Transform } from '../../util/Transform'; import { undoBatch } from '../../util/UndoManager'; -import { DocumentView, DocumentViewSharedProps } from "./DocumentView"; +import { DocumentView, DocumentViewSharedProps } from './DocumentView'; import './LinkDocPreview.scss'; -import React = require("react"); +import React = require('react'); import { DocumentType } from '../../documents/DocumentTypes'; import { DragManager } from '../../util/DragManager'; @@ -27,15 +27,19 @@ interface LinkDocPreviewProps { } @observer export class LinkDocPreview extends React.Component<LinkDocPreviewProps> { - @action public static Clear() { LinkDocPreview.LinkInfo = undefined; } - @action public static SetLinkInfo(info?: LinkDocPreviewProps) { LinkDocPreview.LinkInfo !== info && (LinkDocPreview.LinkInfo = info); } + @action public static Clear() { + LinkDocPreview.LinkInfo = undefined; + } + @action public static SetLinkInfo(info?: LinkDocPreviewProps) { + LinkDocPreview.LinkInfo !== info && (LinkDocPreview.LinkInfo = info); + } _infoRef = React.createRef<HTMLDivElement>(); @observable public static LinkInfo: Opt<LinkDocPreviewProps>; @observable _targetDoc: Opt<Doc>; @observable _linkDoc: Opt<Doc>; @observable _linkSrc: Opt<Doc>; - @observable _toolTipText = ""; + @observable _toolTipText = ''; @observable _hrefInd = 0; @action init() { @@ -47,113 +51,136 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> { if (anchor1 && anchor2) { linkTarget = Doc.AreProtosEqual(anchor1, this._linkSrc) || Doc.AreProtosEqual(anchor1?.annotationOn as Doc, this._linkSrc) ? anchor2 : anchor1; } - if (linkTarget?.annotationOn && linkTarget?.type !== DocumentType.RTF) { // want to show annotation context document if annotation is not text - linkTarget && DocCastAsync(linkTarget.annotationOn).then(action(anno => this._targetDoc = anno)); + if (linkTarget?.annotationOn && linkTarget?.type !== DocumentType.RTF) { + // want to show annotation context document if annotation is not text + linkTarget && DocCastAsync(linkTarget.annotationOn).then(action(anno => (this._targetDoc = anno))); } else { this._targetDoc = linkTarget; } - this._toolTipText = ""; + this._toolTipText = ''; } componentDidUpdate(props: any) { if (props.linkSrc !== this.props.linkSrc || props.linkDoc !== this.props.linkDoc || props.hrefs !== this.props.hrefs) this.init(); } componentDidMount() { this.init(); - document.addEventListener("pointerdown", this.onPointerDown); + document.addEventListener('pointerdown', this.onPointerDown); } componentWillUnmount() { LinkDocPreview.SetLinkInfo(undefined); - document.removeEventListener("pointerdown", this.onPointerDown); + document.removeEventListener('pointerdown', this.onPointerDown); } onPointerDown = (e: PointerEvent) => { !this._infoRef.current?.contains(e.target as any) && LinkDocPreview.Clear(); // close preview when not clicking anywhere other than the info bar of the preview - } + }; @computed get href() { if (this.props.hrefs?.length) { const href = this.props.hrefs[this._hrefInd]; - if (href.indexOf(Doc.localServerPath()) !== 0) { // link to a web page URL -- try to show a preview - if (href.startsWith("https://en.wikipedia.org/wiki/")) { - wiki().page(href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(action(summary => this._toolTipText = summary.substring(0, 500)))); + if (href.indexOf(Doc.localServerPath()) !== 0) { + // link to a web page URL -- try to show a preview + if (href.startsWith('https://en.wikipedia.org/wiki/')) { + wiki() + .page(href.replace('https://en.wikipedia.org/wiki/', '')) + .then(page => page.summary().then(action(summary => (this._toolTipText = summary.substring(0, 500))))); } else { - setTimeout(action(() => this._toolTipText = "url => " + href)); + setTimeout(action(() => (this._toolTipText = 'url => ' + href))); } - } else { // hyperlink to a document .. decode doc id and retrieve from the server. this will trigger vals() being invalidated - const anchorDoc = href.replace(Doc.localServerPath(), "").split("?")[0]; - anchorDoc && DocServer.GetRefField(anchorDoc).then(action(anchor => { - if (anchor instanceof Doc && DocListCast(anchor.links).length) { - this._linkDoc = this._linkDoc ?? DocListCast(anchor.links)[0]; - const automaticLink = this._linkDoc.linkRelationship === LinkManager.AutoKeywords; - if (automaticLink) { // automatic links specify the target in the link info, not the source - const linkTarget = anchor; - this._linkSrc = LinkManager.getOppositeAnchor(this._linkDoc, linkTarget); - this._targetDoc = linkTarget; - } else { - this._linkSrc = anchor; - const linkTarget = LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc); - this._targetDoc = /*linkTarget?.type === DocumentType.MARKER &&*/ linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget; - } - this._toolTipText = ""; - } - })); + } else { + // hyperlink to a document .. decode doc id and retrieve from the server. this will trigger vals() being invalidated + const anchorDoc = href.replace(Doc.localServerPath(), '').split('?')[0]; + anchorDoc && + DocServer.GetRefField(anchorDoc).then( + action(anchor => { + if (anchor instanceof Doc && DocListCast(anchor.links).length) { + this._linkDoc = this._linkDoc ?? DocListCast(anchor.links)[0]; + const automaticLink = this._linkDoc.linkRelationship === LinkManager.AutoKeywords; + if (automaticLink) { + // automatic links specify the target in the link info, not the source + const linkTarget = anchor; + this._linkSrc = LinkManager.getOppositeAnchor(this._linkDoc, linkTarget); + this._targetDoc = linkTarget; + } else { + this._linkSrc = anchor; + const linkTarget = LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc); + this._targetDoc = /*linkTarget?.type === DocumentType.MARKER &&*/ linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget; + } + this._toolTipText = ''; + } + }) + ); } return href; } return undefined; } deleteLink = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(() => this._linkDoc && LinkManager.Instance.deleteLink(this._linkDoc))); - } + setupMoveUpEvents( + this, + e, + returnFalse, + emptyFunction, + undoBatch(() => this._linkDoc && LinkManager.Instance.deleteLink(this._linkDoc)) + ); + }; nextHref = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, action(() => { - const nextHrefInd = (this._hrefInd + 1) % (this.props.hrefs?.length || 1); - if (nextHrefInd !== this._hrefInd) { - this._linkDoc = undefined; - this._hrefInd = nextHrefInd; - } - }), true); - } + setupMoveUpEvents( + this, + e, + returnFalse, + emptyFunction, + action(() => { + const nextHrefInd = (this._hrefInd + 1) % (this.props.hrefs?.length || 1); + if (nextHrefInd !== this._hrefInd) { + this._linkDoc = undefined; + this._hrefInd = nextHrefInd; + } + }), + true + ); + }; followLink = (e: React.PointerEvent) => { if (this._linkDoc && this._linkSrc) { LinkDocPreview.Clear(); LinkManager.FollowLink(this._linkDoc, this._linkSrc, this.props.docProps, false); } else if (this.props.hrefs?.length) { - this.props.docProps?.addDocTab(Docs.Create.WebDocument(this.props.hrefs[0], { title: this.props.hrefs[0], _nativeWidth: 850, _width: 200, _height: 400, useCors: true }), "add:right"); + this.props.docProps?.addDocTab(Docs.Create.WebDocument(this.props.hrefs[0], { title: this.props.hrefs[0], _nativeWidth: 850, _width: 200, _height: 400, useCors: true }), 'add:right'); } - } + }; width = () => { if (!this._targetDoc) return 225; if (this._targetDoc[WidthSym]() < this._targetDoc?.[HeightSym]()) { - return Math.min(225, this._targetDoc[HeightSym]()) * this._targetDoc[WidthSym]() / this._targetDoc[HeightSym](); + return (Math.min(225, this._targetDoc[HeightSym]()) * this._targetDoc[WidthSym]()) / this._targetDoc[HeightSym](); } return Math.min(225, NumCast(this._targetDoc?.[WidthSym](), 225)); - } + }; height = () => { if (!this._targetDoc) return 225; if (this._targetDoc[WidthSym]() > this._targetDoc?.[HeightSym]()) { - return Math.min(225, this._targetDoc[WidthSym]()) * this._targetDoc[HeightSym]() / this._targetDoc[WidthSym](); + return (Math.min(225, this._targetDoc[WidthSym]()) * this._targetDoc[HeightSym]()) / this._targetDoc[WidthSym](); } return Math.min(225, NumCast(this._targetDoc?.[HeightSym](), 225)); - } + }; @computed get previewHeader() { - return !this._linkDoc || !this._targetDoc || !this._linkSrc ? (null) : + return !this._linkDoc || !this._targetDoc || !this._linkSrc ? null : ( <div className="linkDocPreview-info" ref={this._infoRef}> - <div className="linkDocPreview-title" style={{ pointerEvents: "all" }} + <div + className="linkDocPreview-title" + style={{ pointerEvents: 'all' }} onPointerDown={e => { - DragManager.StartDocumentDrag([this._infoRef.current!], - new DragManager.DocumentDragData([this._targetDoc!]), e.pageX, e.pageY); + DragManager.StartDocumentDrag([this._infoRef.current!], new DragManager.DocumentDragData([this._targetDoc!]), e.pageX, e.pageY); e.stopPropagation(); LinkDocPreview.Clear(); }}> - {StrCast(this._targetDoc.title).length > 16 ? StrCast(this._targetDoc.title).substr(0, 16) + "..." : this._targetDoc.title} + {StrCast(this._targetDoc.title).length > 16 ? StrCast(this._targetDoc.title).substr(0, 16) + '...' : StrCast(this._targetDoc.title)} <p className="linkDocPreview-description"> {StrCast(this._linkDoc.description)}</p> </div> - <div className="linkDocPreview-buttonBar" > + <div className="linkDocPreview-buttonBar"> <Tooltip title={<div className="dash-tooltip">Next Link</div>} placement="top"> - <div className="linkDocPreview-button" style={{ background: (this.props.hrefs?.length || 0) <= 1 ? "gray" : "green" }} onPointerDown={this.nextHref}> + <div className="linkDocPreview-button" style={{ background: (this.props.hrefs?.length || 0) <= 1 ? 'gray' : 'green' }} onPointerDown={this.nextHref}> <FontAwesomeIcon className="linkDocPreview-fa-icon" icon="chevron-right" color="white" size="sm" /> </div> </Tooltip> @@ -164,20 +191,24 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> { </div> </Tooltip> </div> - </div>; + </div> + ); } @computed get docPreview() { const href = this.href; // needs to be here to trigger lookup of web pages and docs on server - return (!this._linkDoc || !this._targetDoc || !this._linkSrc) && !this._toolTipText ? (null) : + return (!this._linkDoc || !this._targetDoc || !this._linkSrc) && !this._toolTipText ? null : ( <div className="linkDocPreview-inner"> - {!this.props.showHeader ? (null) : this.previewHeader} - <div className="linkDocPreview-preview-wrapper" style={{ maxHeight: this._toolTipText ? "100%" : undefined, overflow: "auto" }}> - {this._toolTipText ? this._toolTipText : - <DocumentView ref={(r) => { - const targetanchor = this._linkDoc && this._linkSrc && LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc); - targetanchor && this._targetDoc !== targetanchor && r?.focus(targetanchor); - }} + {!this.props.showHeader ? null : this.previewHeader} + <div className="linkDocPreview-preview-wrapper" style={{ maxHeight: this._toolTipText ? '100%' : undefined, overflow: 'auto' }}> + {this._toolTipText ? ( + this._toolTipText + ) : ( + <DocumentView + ref={r => { + const targetanchor = this._linkDoc && this._linkSrc && LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc); + targetanchor && this._targetDoc !== targetanchor && r?.focus(targetanchor); + }} Document={this._targetDoc!} moveDocument={returnFalse} rootSelected={returnFalse} @@ -206,16 +237,19 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> { bringToFront={returnFalse} NativeWidth={Doc.NativeWidth(this._targetDoc) ? () => Doc.NativeWidth(this._targetDoc) : undefined} NativeHeight={Doc.NativeHeight(this._targetDoc) ? () => Doc.NativeHeight(this._targetDoc) : undefined} - />} + /> + )} </div> - </div>; + </div> + ); } render() { const borders = 16; // 8px border on each side - return <div className="linkDocPreview" onPointerDown={this.followLink} - style={{ left: this.props.location[0], top: this.props.location[1], width: this.width() + borders, height: this.height() + borders + (this.props.showHeader ? 37 : 0) }}> - {this.docPreview} - </div>; + return ( + <div className="linkDocPreview" onPointerDown={this.followLink} style={{ left: this.props.location[0], top: this.props.location[1], width: this.width() + borders, height: this.height() + borders + (this.props.showHeader ? 37 : 0) }}> + {this.docPreview} + </div> + ); } -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index 0b7264d79..18a6b5453 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -1,8 +1,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Autocomplete, GoogleMap, GoogleMapProps, Marker } from '@react-google-maps/api'; import { action, computed, IReactionDisposer, observable, ObservableMap } from 'mobx'; -import { observer } from "mobx-react"; -import * as React from "react"; +import { observer } from 'mobx-react'; +import * as React from 'react'; import { Doc, DocListCast, Opt, WidthSym } from '../../../../fields/Doc'; import { Id } from '../../../../fields/FieldSymbols'; import { InkTool } from '../../../../fields/InkField'; @@ -22,17 +22,17 @@ import { Annotation } from '../../pdf/Annotation'; import { SidebarAnnos } from '../../SidebarAnnos'; import { StyleProp } from '../../StyleProvider'; import { FieldView, FieldViewProps } from '../FieldView'; -import "./MapBox.scss"; +import './MapBox.scss'; import { MapBoxInfoWindow } from './MapBoxInfoWindow'; /** * MapBox architecture: * Main component: MapBox.tsx * Supporting Components: SidebarAnnos, CollectionStackingView - * - * MapBox is a node that extends the ViewBoxAnnotatableComponent. Similar to PDFBox and WebBox, it supports interaction between sidebar content and document content. - * The main body of MapBox uses Google Maps API to allow location retrieval, adding map markers, pan and zoom, and open street view. - * Dash Document architecture is integrated with Maps API: When drag and dropping documents with ExifData (gps Latitude and Longitude information) available, + * + * MapBox is a node that extends the ViewBoxAnnotatableComponent. Similar to PDFBox and WebBox, it supports interaction between sidebar content and document content. + * The main body of MapBox uses Google Maps API to allow location retrieval, adding map markers, pan and zoom, and open street view. + * Dash Document architecture is integrated with Maps API: When drag and dropping documents with ExifData (gps Latitude and Longitude information) available, * sidebarAddDocument function checks if the document contains lat & lng information, if it does, then the document is added to both the sidebar and the infowindow (a pop up corresponding to a map marker--pin on map). * The lat and lng field of the document is filled when importing (spec see ConvertDMSToDD method and processFileUpload method in Documents.ts). * A map marker is considered a document that contains a collection with stacking view of documents, it has a lat, lng location, which is passed to Maps API's custom marker (red pin) to be rendered on the google maps @@ -77,26 +77,30 @@ document.head.appendChild(script); // }, // }); - // options for searchbox in Google Maps Places Autocomplete API const options = { - fields: ["formatted_address", "geometry", "name"], // note: level of details is charged by item per retrieval, not recommended to return all fields + fields: ['formatted_address', 'geometry', 'name'], // note: level of details is charged by item per retrieval, not recommended to return all fields strictBounds: false, - types: ["establishment"], // type pf places, subject of change according to user need + types: ['establishment'], // type pf places, subject of change according to user need } as google.maps.places.AutocompleteOptions; @observer export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps & Partial<GoogleMapProps>>() { - private _dropDisposer?: DragManager.DragDropDisposer; private _disposers: { [name: string]: IReactionDisposer } = {}; private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef(); @observable private _overlayAnnoInfo: Opt<Doc>; - showInfo = action((anno: Opt<Doc>) => this._overlayAnnoInfo = anno); - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(MapBox, fieldKey); } - public get SidebarKey() { return this.fieldKey + "-sidebar"; } + showInfo = action((anno: Opt<Doc>) => (this._overlayAnnoInfo = anno)); + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(MapBox, fieldKey); + } + public get SidebarKey() { + return this.fieldKey + '-sidebar'; + } private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean) => void); - @computed get inlineTextAnnotations() { return this.allMapMarkers.filter(a => a.textInlineAnnotations); } + @computed get inlineTextAnnotations() { + return this.allMapMarkers.filter(a => a.textInlineAnnotations); + } @observable private _map: google.maps.Map = null as unknown as google.maps.Map; @observable private selectedPlace: Doc | undefined; @@ -108,14 +112,19 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps @observable private searchMarkers: google.maps.Marker[] = []; @observable private searchBox = new window.google.maps.places.Autocomplete(this.inputRef.current!, options); @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>(); - @computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.SidebarKey]); } - @computed get allMapMarkers() { return DocListCast(this.dataDoc[this.annotationKey]); } + @computed get allSidebarDocs() { + return DocListCast(this.dataDoc[this.SidebarKey]); + } + @computed get allMapMarkers() { + return DocListCast(this.dataDoc[this.annotationKey]); + } @observable private toggleAddMarker = false; private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); - @observable _showSidebar = false; - @computed get SidebarShown() { return this._showSidebar || this.layoutDoc._showSidebar ? true : false; } + @computed get SidebarShown() { + return this._showSidebar || this.layoutDoc._showSidebar ? true : false; + } static _canAnnotate = true; static _hadSelection: boolean = false; @@ -129,109 +138,106 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps @action private setSearchBox = (searchBox: any) => { this.searchBox = searchBox; - } + }; // iterate allMarkers to size, center, and zoom map to contain all markers private fitBounds = (map: google.maps.Map) => { const curBounds = map.getBounds() ?? new window.google.maps.LatLngBounds(); - const isFitting = this.allMapMarkers.reduce((fits, place) => - fits && curBounds?.contains({ lat: NumCast(place.lat), lng: NumCast(place.lng) }), true as boolean); - !isFitting && map.fitBounds(this.allMapMarkers.reduce((bounds, place) => - bounds.extend({ lat: NumCast(place.lat), lng: NumCast(place.lng) }), - new window.google.maps.LatLngBounds())); - } + const isFitting = this.allMapMarkers.reduce((fits, place) => fits && curBounds?.contains({ lat: NumCast(place.lat), lng: NumCast(place.lng) }), true as boolean); + !isFitting && map.fitBounds(this.allMapMarkers.reduce((bounds, place) => bounds.extend({ lat: NumCast(place.lat), lng: NumCast(place.lng) }), new window.google.maps.LatLngBounds())); + }; /** * Custom control for add marker button - * @param controlDiv - * @param map + * @param controlDiv + * @param map */ private CenterControl = () => { - const controlDiv = document.createElement("div"); - controlDiv.className = "mapBox-addMarker"; + const controlDiv = document.createElement('div'); + controlDiv.className = 'mapBox-addMarker'; // Set CSS for the control border. - const controlUI = document.createElement("div"); - controlUI.style.backgroundColor = "#fff"; - controlUI.style.borderRadius = "3px"; - controlUI.style.cursor = "pointer"; - controlUI.style.marginTop = "10px"; - controlUI.style.borderRadius = "4px"; - controlUI.style.marginBottom = "22px"; - controlUI.style.textAlign = "center"; - controlUI.style.position = "absolute"; - controlUI.style.width = "32px"; - controlUI.style.height = "32px"; - controlUI.title = "Click to toggle marker mode. In marker mode, click on map to place a marker."; - - const plIcon = document.createElement("img"); - plIcon.src = "https://cdn4.iconfinder.com/data/icons/wirecons-free-vector-icons/32/add-256.png"; - plIcon.style.color = "rgb(25,25,25)"; - plIcon.style.fontFamily = "Roboto,Arial,sans-serif"; - plIcon.style.fontSize = "16px"; - plIcon.style.lineHeight = "32px"; - plIcon.style.left = "18"; - plIcon.style.top = "15"; - plIcon.style.position = "absolute"; + const controlUI = document.createElement('div'); + controlUI.style.backgroundColor = '#fff'; + controlUI.style.borderRadius = '3px'; + controlUI.style.cursor = 'pointer'; + controlUI.style.marginTop = '10px'; + controlUI.style.borderRadius = '4px'; + controlUI.style.marginBottom = '22px'; + controlUI.style.textAlign = 'center'; + controlUI.style.position = 'absolute'; + controlUI.style.width = '32px'; + controlUI.style.height = '32px'; + controlUI.title = 'Click to toggle marker mode. In marker mode, click on map to place a marker.'; + + const plIcon = document.createElement('img'); + plIcon.src = 'https://cdn4.iconfinder.com/data/icons/wirecons-free-vector-icons/32/add-256.png'; + plIcon.style.color = 'rgb(25,25,25)'; + plIcon.style.fontFamily = 'Roboto,Arial,sans-serif'; + plIcon.style.fontSize = '16px'; + plIcon.style.lineHeight = '32px'; + plIcon.style.left = '18'; + plIcon.style.top = '15'; + plIcon.style.position = 'absolute'; plIcon.width = 14; plIcon.height = 14; - plIcon.innerHTML = "Add"; + plIcon.innerHTML = 'Add'; controlUI.appendChild(plIcon); // Set CSS for the control interior. - const markerIcon = document.createElement("img"); - markerIcon.src = "https://cdn0.iconfinder.com/data/icons/small-n-flat/24/678111-map-marker-1024.png"; - markerIcon.style.color = "rgb(25,25,25)"; - markerIcon.style.fontFamily = "Roboto,Arial,sans-serif"; - markerIcon.style.fontSize = "16px"; - markerIcon.style.lineHeight = "32px"; - markerIcon.style.left = "-2"; - markerIcon.style.top = "1"; + const markerIcon = document.createElement('img'); + markerIcon.src = 'https://cdn0.iconfinder.com/data/icons/small-n-flat/24/678111-map-marker-1024.png'; + markerIcon.style.color = 'rgb(25,25,25)'; + markerIcon.style.fontFamily = 'Roboto,Arial,sans-serif'; + markerIcon.style.fontSize = '16px'; + markerIcon.style.lineHeight = '32px'; + markerIcon.style.left = '-2'; + markerIcon.style.top = '1'; markerIcon.width = 30; markerIcon.height = 30; - markerIcon.style.position = "absolute"; - markerIcon.innerHTML = "Add"; + markerIcon.style.position = 'absolute'; + markerIcon.innerHTML = 'Add'; controlUI.appendChild(markerIcon); // Setup the click event listeners - controlUI.addEventListener("click", () => { + controlUI.addEventListener('click', () => { if (this.toggleAddMarker === true) { this.toggleAddMarker = false; - console.log("add marker button status:" + this.toggleAddMarker); - controlUI.style.backgroundColor = "#fff"; - markerIcon.style.color = "rgb(25,25,25)"; + console.log('add marker button status:' + this.toggleAddMarker); + controlUI.style.backgroundColor = '#fff'; + markerIcon.style.color = 'rgb(25,25,25)'; } else { this.toggleAddMarker = true; - console.log("add marker button status:" + this.toggleAddMarker); - controlUI.style.backgroundColor = "#4476f7"; - markerIcon.style.color = "rgb(255,255,255)"; + console.log('add marker button status:' + this.toggleAddMarker); + controlUI.style.backgroundColor = '#4476f7'; + markerIcon.style.color = 'rgb(255,255,255)'; } }); controlDiv.appendChild(controlUI); return controlDiv; - } + }; /** * Place the marker on google maps & store the empty marker as a MapMarker Document in allMarkers list * @param position - the LatLng position where the marker is placed - * @param map + * @param map */ @action private placeMarker = (position: google.maps.LatLng, map: google.maps.Map) => { const marker = new google.maps.Marker({ position: position, - map: map + map: map, }); map.panTo(position); const mapMarker = Docs.Create.MapMarkerDocument(NumCast(position.lat()), NumCast(position.lng()), false, [], {}); this.addDocument(mapMarker, this.annotationKey); - } + }; _loadPending = true; /** * store a reference to google map instance * setup the drawing manager on the top right corner of map - * fit map bounds to contain all markers - * @param map + * fit map bounds to contain all markers + * @param map */ @action private loadHandler = (map: google.maps.Map) => { @@ -256,7 +262,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps map.setZoom(NumCast(this.dataDoc.mapZoom, 2.5)); map.setCenter(new google.maps.LatLng(NumCast(this.dataDoc.mapLat), NumCast(this.dataDoc.mapLng))); setTimeout(() => { - if (this._loadPending && this._map.getBounds()) { this._loadPending = false; this.layoutDoc.fitContentsToBox && this.fitBounds(this._map); @@ -268,7 +273,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this.placeMarker((e as any).latLng, map); } }); - } + }; @action centered = () => { @@ -278,7 +283,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } this.dataDoc.mapLat = this._map.getCenter()?.lat(); this.dataDoc.mapLng = this._map.getCenter()?.lng(); - } + }; @action zoomChanged = () => { @@ -287,64 +292,64 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this.layoutDoc.fitContentsToBox && this.fitBounds(this._map); } this.dataDoc.mapZoom = this._map.getZoom(); - } + }; /** * Load and render all map markers - * @param marker - * @param place + * @param marker + * @param place */ @action private markerLoadHandler = (marker: google.maps.Marker, place: Doc) => { - place[Id] ? this.markerMap[place[Id]] = marker : null; - } + place[Id] ? (this.markerMap[place[Id]] = marker) : null; + }; /** * on clicking the map marker, set the selected place to the marker document & set infowindowopen to be true - * @param e - * @param place + * @param e + * @param place */ @action private markerClickHandler = (e: google.maps.MapMouseEvent, place: Doc) => { // set which place was clicked this.selectedPlace = place; place.infoWindowOpen = true; - } + }; /** * Called when dragging documents into map sidebar or directly into infowindow; to create a map marker, ref to MapMarkerDocument in Documents.ts - * @param doc - * @param sidebarKey - * @returns + * @param doc + * @param sidebarKey + * @returns */ sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => { - console.log("print all sidebar Docs"); + console.log('print all sidebar Docs'); console.log(this.allSidebarDocs); if (!this.layoutDoc._showSidebar) this.toggleSidebar(); const docs = doc instanceof Doc ? [doc] : doc; docs.forEach(doc => { if (doc.lat !== undefined && doc.lng !== undefined) { const existingMarker = this.allMapMarkers.find(marker => marker.lat === doc.lat && marker.lng === doc.lng); - doc.onClickBehavior = "enterPortal"; + doc.onClickBehavior = 'enterPortal'; if (existingMarker) { - Doc.AddDocToList(existingMarker, "data", doc); + Doc.AddDocToList(existingMarker, 'data', doc); } else { const marker = Docs.Create.MapMarkerDocument(NumCast(doc.lat), NumCast(doc.lng), false, [doc], {}); this.addDocument(marker, this.annotationKey); } } }); //add to annotation list - console.log("sidebaraddDocument"); + console.log('sidebaraddDocument'); console.log(doc); return this.addDocument(doc, sidebarKey); // add to sidebar list - } + }; /** * Removing documents from the sidebar - * @param doc - * @param sidebarKey - * @returns + * @param doc + * @param sidebarKey + * @returns */ sidebarRemoveDocument = (doc: Doc | Doc[], sidebarKey?: string) => { if (this.layoutDoc._showSidebar) this.toggleSidebar(); @@ -354,31 +359,43 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps console.log(this.allSidebarDocs); }); return this.removeDocument(doc, sidebarKey); - } + }; /** * Toggle sidebar onclick the tiny comment button on the top right corner - * @param e + * @param e */ sidebarBtnDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, (e, down, delta) => { - const localDelta = this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1).transformDirection(delta[0], delta[1]); - const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]); - const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth); - const ratio = (curNativeWidth + localDelta[0] / (this.props.scaling?.() || 1)) / nativeWidth; - if (ratio >= 1) { - this.layoutDoc.nativeWidth = nativeWidth * ratio; - this.layoutDoc._width = this.layoutDoc[WidthSym]() + localDelta[0]; - this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth; - } - return false; - }, emptyFunction, this.toggleSidebar); - } - - sidebarWidth = () => Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth(); - @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); } - @computed get sidebarColor() { return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "#e4e4e4")); } + setupMoveUpEvents( + this, + e, + (e, down, delta) => { + const localDelta = this.props + .ScreenToLocalTransform() + .scale(this.props.scaling?.() || 1) + .transformDirection(delta[0], delta[1]); + const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '-nativeWidth']); + const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth); + const ratio = (curNativeWidth + localDelta[0] / (this.props.scaling?.() || 1)) / nativeWidth; + if (ratio >= 1) { + this.layoutDoc.nativeWidth = nativeWidth * ratio; + this.layoutDoc._width = this.layoutDoc[WidthSym]() + localDelta[0]; + this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth; + } + return false; + }, + emptyFunction, + this.toggleSidebar + ); + }; + sidebarWidth = () => (Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100) * this.props.PanelWidth(); + @computed get sidebarWidthPercent() { + return StrCast(this.layoutDoc._sidebarWidthPercent, '0%'); + } + @computed get sidebarColor() { + return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + '-backgroundColor'], '#e4e4e4')); + } /** * function that reads the place inputed from searchbox, then zoom in on the location that's been autocompleted; @@ -414,7 +431,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps }; // put temporary cutomized marker on searched location - this.searchMarkers.forEach((marker) => { + this.searchMarkers.forEach(marker => { marker.setMap(null); }); this.searchMarkers = []; @@ -426,7 +443,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps position: place.geometry.location, }) ); - } + }; /** * Handles toggle of sidebar on click the little comment button @@ -435,95 +452,99 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps TraceMobx(); const annotated = DocListCast(this.dataDoc[this.SidebarKey]).filter(d => d?.author).length; const color = !annotated ? Colors.WHITE : Colors.BLACK; - const backgroundColor = !annotated ? this.sidebarWidth() ? Colors.MEDIUM_BLUE : Colors.BLACK : this.props.styleProvider?.(this.rootDoc, this.props as any, StyleProp.WidgetColor + (annotated ? ":annotated" : "")); - return (!annotated) ? (null) : - <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} + const backgroundColor = !annotated ? (this.sidebarWidth() ? Colors.MEDIUM_BLUE : Colors.BLACK) : this.props.styleProvider?.(this.rootDoc, this.props as any, StyleProp.WidgetColor + (annotated ? ':annotated' : '')); + return !annotated ? null : ( + <div + className="formattedTextBox-sidebar-handle" + onPointerDown={this.sidebarDown} style={{ left: `max(0px, calc(100% - ${this.sidebarWidthPercent} - 17px))`, backgroundColor: backgroundColor, color: color, - opacity: annotated ? 1 : undefined - }} > - <FontAwesomeIcon icon={"comment-alt"} /> - </div>; + opacity: annotated ? 1 : undefined, + }}> + <FontAwesomeIcon icon={'comment-alt'} /> + </div> + ); } // TODO: Adding highlight box layer to Maps @action toggleSidebar = () => { const prevWidth = this.sidebarWidth(); - this.layoutDoc._showSidebar = ((this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, "0%") === "0%" ? "50%" : "0%")) !== "0%"; + this.layoutDoc._showSidebar = (this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, '0%') === '0%' ? '50%' : '0%') !== '0%'; this.layoutDoc._width = this.layoutDoc._showSidebar ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth); - } + }; sidebarDown = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), false); - } + }; sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => { const bounds = this._ref.current!.getBoundingClientRect(); - this.layoutDoc._sidebarWidthPercent = "" + 100 * Math.max(0, (1 - (e.clientX - bounds.left) / bounds.width)) + "%"; - this.layoutDoc._showSidebar = this.layoutDoc._sidebarWidthPercent !== "0%"; + this.layoutDoc._sidebarWidthPercent = '' + 100 * Math.max(0, 1 - (e.clientX - bounds.left) / bounds.width) + '%'; + this.layoutDoc._showSidebar = this.layoutDoc._sidebarWidthPercent !== '0%'; e.preventDefault(); return false; - } + }; - setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => this._setPreviewCursor = func; + setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => (this._setPreviewCursor = func); @action onMarqueeDown = (e: React.PointerEvent) => { if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.ActiveTool)) { - setupMoveUpEvents(this, e, action(e => { - MarqueeAnnotator.clearAnnotations(this._savedAnnotations); - this._marqueeing = [e.clientX, e.clientY]; - return true; - }), returnFalse, () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations), false); + setupMoveUpEvents( + this, + e, + action(e => { + MarqueeAnnotator.clearAnnotations(this._savedAnnotations); + this._marqueeing = [e.clientX, e.clientY]; + return true; + }), + returnFalse, + () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations), + false + ); } - } + }; @action finishMarquee = (x?: number, y?: number) => { this._marqueeing = undefined; this._isAnnotating = false; x !== undefined && y !== undefined && this._setPreviewCursor?.(x, y, false, false); - } + }; addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => { return this.addDocument(doc, annotationKey); - } + }; pointerEvents = () => { - return this.props.isContentActive() && this.props.pointerEvents?.() !== "none" && !MarqueeOptionsMenu.Instance.isShown() ? - "all" : - SnappingManager.GetIsDragging() ? - undefined : "none"; - } + return this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : SnappingManager.GetIsDragging() ? undefined : 'none'; + }; @computed get annotationLayer() { - return <div className="mapBox-annotationLayer" style={{ height: Doc.NativeHeight(this.Document) || undefined }} ref={this._annotationLayer}> - {this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno => - <Annotation key={`${anno[Id]}-annotation`} {...this.props} - fieldKey={this.annotationKey} pointerEvents={this.pointerEvents} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} />)} - </div>; + return ( + <div className="mapBox-annotationLayer" style={{ height: Doc.NativeHeight(this.Document) || undefined }} ref={this._annotationLayer}> + {this.inlineTextAnnotations + .sort((a, b) => NumCast(a.y) - NumCast(b.y)) + .map(anno => ( + <Annotation key={`${anno[Id]}-annotation`} {...this.props} fieldKey={this.annotationKey} pointerEvents={this.pointerEvents} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} /> + ))} + </div> + ); } getAnchor = () => { - const anchor = - AnchorMenu.Instance?.GetAnchor(this._savedAnnotations) ?? - this.rootDoc; + const anchor = AnchorMenu.Instance?.GetAnchor(this._savedAnnotations) ?? this.rootDoc; return anchor; - } + }; /** * render contents in allMapMarkers (e.g. images with exifData) into google maps as map marker - * @returns + * @returns */ private renderMarkers = () => { return this.allMapMarkers.map(place => ( - <Marker - key={place[Id]} - position={{ lat: NumCast(place.lat), lng: NumCast(place.lng) }} - onLoad={marker => this.markerLoadHandler(marker, place)} - onClick={(e: google.maps.MapMouseEvent) => this.markerClickHandler(e, place)} - /> + <Marker key={place[Id]} position={{ lat: NumCast(place.lat), lng: NumCast(place.lng) }} onLoad={marker => this.markerLoadHandler(marker, place)} onClick={(e: google.maps.MapMouseEvent) => this.markerClickHandler(e, place)} /> )); - } + }; // TODO: auto center on select a document in the sidebar private handleMapCenter = (map: google.maps.Map) => { @@ -531,7 +552,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps // if (SelectionManager.Views().lastElement()) { // console.log(SelectionManager.Views().lastElement()); // } - } + }; panelWidth = () => this.props.PanelWidth() / (this.props.scaling?.() || 1) - this.sidebarWidth(); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0); panelHeight = () => this.props.PanelHeight() / (this.props.scaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document); @@ -543,7 +564,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick; savedAnnotations = () => this._savedAnnotations; render() { - const renderAnnotations = (docFilters?: () => string[]) => (null); + const renderAnnotations = (docFilters?: () => string[]) => null; // bcz: commmented this out. Otherwise, any documents that are rendered with an InfoWindow of a marker // will also be rendered as freeform annotations on the map. However, it doesn't seem that rendering // freeform documents on the map does anything anyway, so getting rid of it for now. Also, since documents @@ -571,88 +592,88 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps // addDocument={this.sidebarAddDocument} // childPointerEvents={true} // pointerEvents={CurrentUserUtils.ActiveTool !== InkTool.None || this._isAnnotating || SnappingManager.GetIsDragging() ? "all" : "none"} />; - return <div className="mapBox" ref={this._ref}> - {/*console.log(apiKey)*/} - {/* <LoadScript + return ( + <div className="mapBox" ref={this._ref}> + {/*console.log(apiKey)*/} + {/* <LoadScript googleMapsApiKey={apiKey!} libraries={['places', 'drawing']} > */} - <div className="mapBox-wrapper" - onWheel={e => e.stopPropagation()} - onPointerDown={e => (e.button === 0 && !e.ctrlKey) && e.stopPropagation()} - style={{ width: `calc(100% - ${this.sidebarWidthPercent})` }}> - - <div style={{ mixBlendMode: "multiply" }}> - {renderAnnotations(this.transparentFilter)} + <div className="mapBox-wrapper" onWheel={e => e.stopPropagation()} onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()} style={{ width: `calc(100% - ${this.sidebarWidthPercent})` }}> + <div style={{ mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div> + {renderAnnotations(this.opaqueFilter)} + {SnappingManager.GetIsDragging() ? null : renderAnnotations()} + {this.annotationLayer} + <GoogleMap mapContainerStyle={mapContainerStyle} onZoomChanged={this.zoomChanged} onCenterChanged={this.centered} onLoad={this.loadHandler} options={mapOptions}> + <Autocomplete onLoad={this.setSearchBox} onPlaceChanged={this.handlePlaceChanged}> + <input className="mapBox-input" ref={this.inputRef} type="text" placeholder="Enter location" /> + </Autocomplete> + + {this.renderMarkers()} + {this.allMapMarkers + .filter(marker => marker.infoWindowOpen) + .map(marker => ( + <MapBoxInfoWindow + key={marker[Id]} + {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit} + place={marker} + markerMap={this.markerMap} + PanelWidth={this.infoWidth} + PanelHeight={this.infoHeight} + moveDocument={this.moveDocument} + isAnyChildContentActive={this.isAnyChildContentActive} + whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} + /> + ))} + {/* {this.handleMapCenter(this._map)} */} + </GoogleMap> + {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : ( + <MarqueeAnnotator + rootDoc={this.rootDoc} + anchorMenuClick={this.anchorMenuClick} + scrollTop={0} + down={this._marqueeing} + scaling={returnOne} + addDocument={this.addDocumentWrapper} + docView={this.props.docViewPath().lastElement()} + finishMarquee={this.finishMarquee} + savedAnnotations={this.savedAnnotations} + annotationLayer={this._annotationLayer.current} + mainCont={this._mainCont.current} + /> + )} </div> - {renderAnnotations(this.opaqueFilter)} - {SnappingManager.GetIsDragging() ? (null) : renderAnnotations()} - {this.annotationLayer} - <GoogleMap - mapContainerStyle={mapContainerStyle} - onZoomChanged={this.zoomChanged} - onCenterChanged={this.centered} - onLoad={this.loadHandler} - options={mapOptions} - > - <Autocomplete - onLoad={this.setSearchBox} - onPlaceChanged={this.handlePlaceChanged}> - <input className="mapBox-input" ref={this.inputRef} type="text" placeholder="Enter location" /> - </Autocomplete> - - {this.renderMarkers()} - {this.allMapMarkers.filter(marker => marker.infoWindowOpen).map(marker => <MapBoxInfoWindow key={marker[Id]} - {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit} - place={marker} - markerMap={this.markerMap} - PanelWidth={this.infoWidth} - PanelHeight={this.infoHeight} - moveDocument={this.moveDocument} - isAnyChildContentActive={this.isAnyChildContentActive} + {/* </LoadScript > */} + <div className="mapBox-sidebar" style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}> + <SidebarAnnos + ref={this._sidebarRef} + {...this.props} + fieldKey={this.fieldKey} + rootDoc={this.rootDoc} + layoutDoc={this.layoutDoc} + dataDoc={this.dataDoc} + showSidebar={this.SidebarShown} + nativeWidth={NumCast(this.layoutDoc._nativeWidth)} whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} - />)} - {this.handleMapCenter(this._map)} - </GoogleMap> - {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) : - <MarqueeAnnotator rootDoc={this.rootDoc} - anchorMenuClick={this.anchorMenuClick} - scrollTop={0} - down={this._marqueeing} scaling={returnOne} - addDocument={this.addDocumentWrapper} - docView={this.props.docViewPath().lastElement()} - finishMarquee={this.finishMarquee} - savedAnnotations={this.savedAnnotations} - annotationLayer={this._annotationLayer.current} - mainCont={this._mainCont.current} />} - </div> - {/* </LoadScript > */} - <div className="mapBox-sidebar" - style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}> - <SidebarAnnos ref={this._sidebarRef} - {...this.props} - fieldKey={this.fieldKey} - rootDoc={this.rootDoc} - layoutDoc={this.layoutDoc} - dataDoc={this.dataDoc} - showSidebar={this.SidebarShown} - nativeWidth={NumCast(this.layoutDoc._nativeWidth)} - whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} - PanelWidth={this.sidebarWidth} - sidebarAddDocument={this.sidebarAddDocument} - moveDocument={this.moveDocument} - removeDocument={this.sidebarRemoveDocument} - /> - </div> - <div className="mapBox-overlayButton-sidebar" key="sidebar" title="Toggle Sidebar" - style={{ - display: !this.props.isContentActive() ? "none" : undefined, - top: StrCast(this.rootDoc._showTitle) === "title" ? 20 : 5, - backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK - }} - onPointerDown={this.sidebarBtnDown} > - <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={"comment-alt"} size="sm" /> + PanelWidth={this.sidebarWidth} + sidebarAddDocument={this.sidebarAddDocument} + moveDocument={this.moveDocument} + removeDocument={this.sidebarRemoveDocument} + /> + </div> + <div + className="mapBox-overlayButton-sidebar" + key="sidebar" + title="Toggle Sidebar" + style={{ + display: !this.props.isContentActive() ? 'none' : undefined, + top: StrCast(this.rootDoc._showTitle) === 'title' ? 20 : 5, + backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK, + }} + onPointerDown={this.sidebarBtnDown}> + <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" /> + </div> </div> - </div>; + ); } } diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 1fb3cef2a..5b9d90780 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -1,12 +1,12 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; -import { observer } from "mobx-react"; -import * as Pdfjs from "pdfjs-dist"; -import "pdfjs-dist/web/pdf_viewer.css"; -import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../fields/Doc"; +import { observer } from 'mobx-react'; +import * as Pdfjs from 'pdfjs-dist'; +import 'pdfjs-dist/web/pdf_viewer.css'; +import { Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types'; -import { ImageField, PdfField } from "../../../fields/URLField"; +import { ImageField, PdfField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; import { emptyFunction, returnOne, setupMoveUpEvents, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; @@ -15,25 +15,27 @@ import { KeyCodes } from '../../util/KeyCodes'; import { undoBatch, UndoManager } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; -import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from "../DocComponent"; +import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent'; import { Colors } from '../global/globalEnums'; -import { CreateImage } from "../nodes/WebBoxRenderer"; +import { CreateImage } from '../nodes/WebBoxRenderer'; import { AnchorMenu } from '../pdf/AnchorMenu'; -import { PDFViewer } from "../pdf/PDFViewer"; +import { PDFViewer } from '../pdf/PDFViewer'; import { SidebarAnnos } from '../SidebarAnnos'; import { FieldView, FieldViewProps } from './FieldView'; import { ImageBox } from './ImageBox'; -import "./PDFBox.scss"; +import './PDFBox.scss'; import { VideoBox } from './VideoBox'; -import React = require("react"); +import React = require('react'); import { CollectionFreeFormView } from '../collections/collectionFreeForm'; @observer export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() { - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PDFBox, fieldKey); } + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(PDFBox, fieldKey); + } public static openSidebarWidth = 250; public static sidebarResizerWidth = 5; - private _searchString: string = ""; + private _searchString: string = ''; private _initialScrollTarget: Opt<Doc>; private _pdfViewer: PDFViewer | undefined; private _searchRef = React.createRef<HTMLInputElement>(); @@ -44,8 +46,12 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps @observable private _pdf: Opt<Pdfjs.PDFDocumentProxy>; @observable private _pageControls = false; - @computed get pdfUrl() { return Cast(this.dataDoc[this.props.fieldKey], PdfField); } - @computed get pdfThumb() { return ImageCast(this.layoutDoc["thumb-frozen"], ImageCast(this.layoutDoc.thumb))?.url; } + @computed get pdfUrl() { + return Cast(this.dataDoc[this.props.fieldKey], PdfField); + } + @computed get pdfThumb() { + return ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb))?.url; + } constructor(props: any) { super(props); @@ -53,8 +59,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps const nh = Doc.NativeHeight(this.Document, this.dataDoc) || 1200; !this.Document._fitWidth && (this.Document._height = this.Document[WidthSym]() * (nh / nw)); if (this.pdfUrl) { - if (PDFBox.pdfcache.get(this.pdfUrl.url.href)) runInAction(() => this._pdf = PDFBox.pdfcache.get(this.pdfUrl!.url.href)); - else if (PDFBox.pdfpromise.get(this.pdfUrl.url.href)) PDFBox.pdfpromise.get(this.pdfUrl.url.href)?.then(action((pdf: any) => this._pdf = pdf)); + if (PDFBox.pdfcache.get(this.pdfUrl.url.href)) runInAction(() => (this._pdf = PDFBox.pdfcache.get(this.pdfUrl!.url.href))); + else if (PDFBox.pdfpromise.get(this.pdfUrl.url.href)) PDFBox.pdfpromise.get(this.pdfUrl.url.href)?.then(action((pdf: any) => (this._pdf = pdf))); } } @@ -65,15 +71,14 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } } - if (oldDiv.className === "pdfBox-ui" || - oldDiv.className === "pdfViewerDash-overlay-inking") { - newDiv.style.display = "none"; + if (oldDiv.className === 'pdfBox-ui' || oldDiv.className === 'pdfViewerDash-overlay-inking') { + newDiv.style.display = 'none'; } - if (newDiv && newDiv.style) newDiv.style.overflow = "hidden"; + if (newDiv && newDiv.style) newDiv.style.overflow = 'hidden'; if (oldDiv instanceof HTMLCanvasElement) { const canvas = oldDiv; const img = document.createElement('img'); // create a Image Element - img.src = canvas.toDataURL(); //image sourcez + img.src = canvas.toDataURL(); //image sourcez img.style.width = canvas.style.width; img.style.height = canvas.style.height; const newCan = newDiv as HTMLCanvasElement; @@ -81,20 +86,20 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps parEle.removeChild(newCan); parEle.appendChild(img); } - } + }; crop = (region: Doc | undefined, addCrop?: boolean) => { if (!region) return; const cropping = Doc.MakeCopy(region, true); Doc.GetProto(region).lockedPosition = true; - Doc.GetProto(region).title = "region:" + this.rootDoc.title; + Doc.GetProto(region).title = 'region:' + this.rootDoc.title; Doc.GetProto(region).isPushpin = true; this.addDocument(region); const docViewContent = this.props.docViewPath().lastElement().ContentDiv!; const newDiv = docViewContent.cloneNode(true) as HTMLDivElement; - newDiv.style.width = (this.layoutDoc[WidthSym]()).toString(); - newDiv.style.height = (this.layoutDoc[HeightSym]()).toString(); + newDiv.style.width = this.layoutDoc[WidthSym]().toString(); + newDiv.style.height = this.layoutDoc[HeightSym]().toString(); this.replaceCanvases(docViewContent, newDiv); const htmlString = this._pdfViewer?._mainCont.current && new XMLSerializer().serializeToString(newDiv); @@ -103,7 +108,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps const anchw = cropping[WidthSym]() * (this.props.scaling?.() || 1); const anchh = cropping[HeightSym]() * (this.props.scaling?.() || 1); const viewScale = 1; - cropping.title = "crop: " + this.rootDoc.title; + cropping.title = 'crop: ' + this.rootDoc.title; cropping.x = NumCast(this.rootDoc.x) + NumCast(this.rootDoc._width); cropping.y = NumCast(this.rootDoc.y); cropping._width = anchw; @@ -114,100 +119,113 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps croppingProto.isPrototype = true; croppingProto.proto = Cast(this.rootDoc.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO croppingProto.type = DocumentType.IMG; - croppingProto.layout = ImageBox.LayoutString("data"); - croppingProto.data = new ImageField(Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")); - croppingProto["data-nativeWidth"] = anchw; - croppingProto["data-nativeHeight"] = anchh; + croppingProto.layout = ImageBox.LayoutString('data'); + croppingProto.data = new ImageField(Utils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png')); + croppingProto['data-nativeWidth'] = anchw; + croppingProto['data-nativeHeight'] = anchh; if (addCrop) { - DocUtils.MakeLink({ doc: region }, { doc: cropping }, "cropped image", ""); + DocUtils.MakeLink({ doc: region }, { doc: cropping }, 'cropped image', ''); } this.props.bringToFront(cropping); CreateImage( - "", + '', document.styleSheets, htmlString, anchw, anchh, - NumCast(region.y) * this.props.PanelWidth() / NumCast(this.rootDoc[this.fieldKey + "-nativeWidth"]), - NumCast(region.x) * this.props.PanelWidth() / NumCast(this.rootDoc[this.fieldKey + "-nativeWidth"]), + (NumCast(region.y) * this.props.PanelWidth()) / NumCast(this.rootDoc[this.fieldKey + '-nativeWidth']), + (NumCast(region.x) * this.props.PanelWidth()) / NumCast(this.rootDoc[this.fieldKey + '-nativeWidth']), 4 - ).then - ((data_url: any) => { - VideoBox.convertDataUri(data_url, region[Id]).then( - returnedfilename => setTimeout(action(() => { - croppingProto.data = new ImageField(returnedfilename); - }), 500)); + ) + .then((data_url: any) => { + VideoBox.convertDataUri(data_url, region[Id]).then(returnedfilename => + setTimeout( + action(() => { + croppingProto.data = new ImageField(returnedfilename); + }), + 500 + ) + ); }) .catch(function (error: any) { console.error('oops, something went wrong!', error); }); - return cropping; - } + }; updateIcon = () => { // currently we render pdf icons as text labels const docViewContent = this.props.docViewPath().lastElement().ContentDiv!; - const filename = this.layoutDoc[Id] + "-icon" + (new Date()).getTime(); - this._pdfViewer?._mainCont.current && CollectionFreeFormView.UpdateIcon( - filename, docViewContent, - this.layoutDoc[WidthSym](), this.layoutDoc[HeightSym](), - this.props.PanelWidth(), this.props.PanelHeight(), - NumCast(this.layoutDoc._scrollTop), - NumCast(this.rootDoc[this.fieldKey + "-nativeHeight"], 1), - true, - this.layoutDoc[Id] + "-icon", - (iconFile:string, nativeWidth:number, nativeHeight:number) => { - setTimeout(() => { - this.dataDoc.icon = new ImageField(iconFile); - this.dataDoc["icon-nativeWidth"] = nativeWidth; - this.dataDoc["icon-nativeHeight"] = nativeHeight; - }, 500); - }); - } + const filename = this.layoutDoc[Id] + '-icon' + new Date().getTime(); + this._pdfViewer?._mainCont.current && + CollectionFreeFormView.UpdateIcon( + filename, + docViewContent, + this.layoutDoc[WidthSym](), + this.layoutDoc[HeightSym](), + this.props.PanelWidth(), + this.props.PanelHeight(), + NumCast(this.layoutDoc._scrollTop), + NumCast(this.rootDoc[this.fieldKey + '-nativeHeight'], 1), + true, + this.layoutDoc[Id] + '-icon', + (iconFile: string, nativeWidth: number, nativeHeight: number) => { + setTimeout(() => { + this.dataDoc.icon = new ImageField(iconFile); + this.dataDoc['icon-nativeWidth'] = nativeWidth; + this.dataDoc['icon-nativeHeight'] = nativeHeight; + }, 500); + } + ); + }; - componentWillUnmount() { this._selectReactionDisposer?.(); } + componentWillUnmount() { + this._selectReactionDisposer?.(); + } componentDidMount() { this.props.setContentView?.(this); - this._selectReactionDisposer = reaction(() => this.props.isSelected(), + this._selectReactionDisposer = reaction( + () => this.props.isSelected(), () => { - document.removeEventListener("keydown", this.onKeyDown); - this.props.isSelected(true) && document.addEventListener("keydown", this.onKeyDown); - }, { fireImmediately: true }); + document.removeEventListener('keydown', this.onKeyDown); + this.props.isSelected(true) && document.addEventListener('keydown', this.onKeyDown); + }, + { fireImmediately: true } + ); } scrollFocus = (doc: Doc, smooth: boolean) => { let didToggle = false; - if (DocListCast(this.props.Document[this.fieldKey + "-sidebar"]).includes(doc) && !this.SidebarShown) { + if (DocListCast(this.props.Document[this.fieldKey + '-sidebar']).includes(doc) && !this.SidebarShown) { this.toggleSidebar(!smooth); didToggle = true; } if (this._sidebarRef?.current?.makeDocUnfiltered(doc)) return 1; this._initialScrollTarget = doc; return this._pdfViewer?.scrollFocus(doc, smooth) ?? (didToggle ? 1 : undefined); - } + }; getAnchor = () => { const anchor = this._pdfViewer?._getAnchor(this._pdfViewer.savedAnnotations()) ?? Docs.Create.TextanchorDocument({ - title: StrCast(this.rootDoc.title + "@" + NumCast(this.layoutDoc._scrollTop)?.toFixed(0)), + title: StrCast(this.rootDoc.title + '@' + NumCast(this.layoutDoc._scrollTop)?.toFixed(0)), y: NumCast(this.layoutDoc._scrollTop), - unrendered: true + unrendered: true, }); this.addDocument(anchor); return anchor; - } + }; @action loaded = (nw: number, nh: number, np: number) => { - this.dataDoc[this.props.fieldKey + "-numPages"] = np; - Doc.SetNativeWidth(this.dataDoc, Math.max(Doc.NativeWidth(this.dataDoc), nw * 96 / 72)); - Doc.SetNativeHeight(this.dataDoc, nh * 96 / 72); + this.dataDoc[this.props.fieldKey + '-numPages'] = np; + Doc.SetNativeWidth(this.dataDoc, Math.max(Doc.NativeWidth(this.dataDoc), (nw * 96) / 72)); + Doc.SetNativeHeight(this.dataDoc, (nh * 96) / 72); this.layoutDoc._height = this.layoutDoc[WidthSym]() / (Doc.NativeAspect(this.dataDoc) || 1); !this.Document._fitWidth && (this.Document._height = this.Document[WidthSym]() * (nh / nw)); - } + }; public search = action((searchString: string, bwd?: boolean, clear: boolean = false) => { if (!this._searching && !clear) { @@ -225,19 +243,23 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps public backPage = () => { this.Document._curPage = Math.max(1, (NumCast(this.Document._curPage) || 1) - 1); return true; - } + }; public forwardPage = () => { - this.Document._curPage = Math.min(NumCast(this.dataDoc[this.props.fieldKey + "-numPages"]), (NumCast(this.Document._curPage) || 1) + 1); + this.Document._curPage = Math.min(NumCast(this.dataDoc[this.props.fieldKey + '-numPages']), (NumCast(this.Document._curPage) || 1) + 1); return true; - } - public gotoPage = (p: number) => this.Document._curPage = p; + }; + public gotoPage = (p: number) => (this.Document._curPage = p); @undoBatch onKeyDown = action((e: KeyboardEvent) => { let processed = false; switch (e.key) { - case "PageDown": processed = this.forwardPage(); break; - case "PageUp": processed = this.backPage(); break; + case 'PageDown': + processed = this.forwardPage(); + break; + case 'PageUp': + processed = this.backPage(); + break; } if (processed) { e.stopImmediatePropagation(); @@ -251,131 +273,163 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this.scrollFocus(this._initialScrollTarget, false); this._initialScrollTarget = undefined; } - } - searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => this._searchString = e.currentTarget.value; + }; + searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => (this._searchString = e.currentTarget.value); // adding external documents; to sidebar key // if (doc.Geolocation) this.addDocument(doc, this.fieldkey+"-annotation") sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => { if (!this.layoutDoc._showSidebar) this.toggleSidebar(); return this.addDocument(doc, sidebarKey); - } - sidebarBtnDown = (e: React.PointerEvent, onButton: boolean) => { // onButton determines whether the width of the pdf box changes, or just the ratio of the sidebar to the pdf - const batch = UndoManager.StartBatch("sidebar"); - setupMoveUpEvents(this, e, (e, down, delta) => { - const localDelta = this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1).transformDirection(delta[0], delta[1]); - const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]); - const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth); - const ratio = (curNativeWidth + (onButton ? 1 : -1) * localDelta[0] / (this.props.scaling?.() || 1)) / nativeWidth; - if (ratio >= 1) { - this.layoutDoc.nativeWidth = nativeWidth * ratio; - onButton && (this.layoutDoc._width = this.layoutDoc[WidthSym]() + localDelta[0]); - this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth; - } - return false; - }, () => batch.end(), () => this.toggleSidebar()); - } + }; + sidebarBtnDown = (e: React.PointerEvent, onButton: boolean) => { + // onButton determines whether the width of the pdf box changes, or just the ratio of the sidebar to the pdf + const batch = UndoManager.StartBatch('sidebar'); + setupMoveUpEvents( + this, + e, + (e, down, delta) => { + const localDelta = this.props + .ScreenToLocalTransform() + .scale(this.props.scaling?.() || 1) + .transformDirection(delta[0], delta[1]); + const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '-nativeWidth']); + const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth); + const ratio = (curNativeWidth + ((onButton ? 1 : -1) * localDelta[0]) / (this.props.scaling?.() || 1)) / nativeWidth; + if (ratio >= 1) { + this.layoutDoc.nativeWidth = nativeWidth * ratio; + onButton && (this.layoutDoc._width = this.layoutDoc[WidthSym]() + localDelta[0]); + this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth; + } + return false; + }, + () => batch.end(), + () => this.toggleSidebar() + ); + }; @observable _previewNativeWidth: Opt<number> = undefined; @observable _previewWidth: Opt<number> = undefined; toggleSidebar = action((preview: boolean = false) => { - const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]); + const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '-nativeWidth']); const sideratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? PDFBox.openSidebarWidth : 0) + nativeWidth) / nativeWidth; const pdfratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? PDFBox.openSidebarWidth + PDFBox.sidebarResizerWidth : 0) + nativeWidth) / nativeWidth; const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth); if (preview) { this._previewNativeWidth = nativeWidth * sideratio; - this._previewWidth = this.layoutDoc[WidthSym]() * nativeWidth * sideratio / curNativeWidth; + this._previewWidth = (this.layoutDoc[WidthSym]() * nativeWidth * sideratio) / curNativeWidth; this._showSidebar = true; - } - else { + } else { this.layoutDoc.nativeWidth = nativeWidth * pdfratio; - this.layoutDoc._width = this.layoutDoc[WidthSym]() * nativeWidth * pdfratio / curNativeWidth; + this.layoutDoc._width = (this.layoutDoc[WidthSym]() * nativeWidth * pdfratio) / curNativeWidth; this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth; } }); settingsPanel() { - const pageBtns = <> - <button className="pdfBox-backBtn" key="back" title="Page Back" - onPointerDown={e => e.stopPropagation()} onClick={this.backPage} > - <FontAwesomeIcon style={{ color: "white" }} icon={"arrow-left"} size="sm" /> - </button> - <button className="pdfBox-fwdBtn" key="fwd" title="Page Forward" - onPointerDown={e => e.stopPropagation()} onClick={this.forwardPage} > - <FontAwesomeIcon style={{ color: "white" }} icon={"arrow-right"} size="sm" /> - </button> - </>; - const searchTitle = `${!this._searching ? "Open" : "Close"} Search Bar`; + const pageBtns = ( + <> + <button className="pdfBox-backBtn" key="back" title="Page Back" onPointerDown={e => e.stopPropagation()} onClick={this.backPage}> + <FontAwesomeIcon style={{ color: 'white' }} icon={'arrow-left'} size="sm" /> + </button> + <button className="pdfBox-fwdBtn" key="fwd" title="Page Forward" onPointerDown={e => e.stopPropagation()} onClick={this.forwardPage}> + <FontAwesomeIcon style={{ color: 'white' }} icon={'arrow-right'} size="sm" /> + </button> + </> + ); + const searchTitle = `${!this._searching ? 'Open' : 'Close'} Search Bar`; const curPage = NumCast(this.Document._curPage) || 1; - return !this.props.isContentActive() || this._pdfViewer?.isAnnotating ? (null) : - <div className="pdfBox-ui" onKeyDown={e => [KeyCodes.BACKSPACE, KeyCodes.DELETE].includes(e.keyCode) ? e.stopPropagation() : true} - onPointerDown={e => e.stopPropagation()} style={{ display: this.props.isContentActive() ? "flex" : "none" }}> - <div className="pdfBox-overlayCont" onPointerDown={(e) => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}> + return !this.props.isContentActive() || this._pdfViewer?.isAnnotating ? null : ( + <div + className="pdfBox-ui" + onKeyDown={e => ([KeyCodes.BACKSPACE, KeyCodes.DELETE].includes(e.keyCode) ? e.stopPropagation() : true)} + onPointerDown={e => e.stopPropagation()} + style={{ display: this.props.isContentActive() ? 'flex' : 'none' }}> + <div className="pdfBox-overlayCont" onPointerDown={e => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}> <button className="pdfBox-overlayButton" title={searchTitle} /> - <input className="pdfBox-searchBar" placeholder="Search" ref={this._searchRef} onChange={this.searchStringChanged} + <input + className="pdfBox-searchBar" + placeholder="Search" + ref={this._searchRef} + onChange={this.searchStringChanged} onKeyDown={e => { e.stopPropagation(); e.keyCode === KeyCodes.ENTER && this.search(this._searchString, e.shiftKey); - }} /> + }} + /> <button className="pdfBox-search" title="Search" onClick={e => this.search(this._searchString, e.shiftKey)}> <FontAwesomeIcon icon="search" size="sm" /> </button> - <button className="pdfBox-prevIcon" title="Previous Annotation" onClick={this.prevAnnotation} > - <FontAwesomeIcon icon={"arrow-up"} size="lg" /> + <button className="pdfBox-prevIcon" title="Previous Annotation" onClick={this.prevAnnotation}> + <FontAwesomeIcon icon={'arrow-up'} size="lg" /> </button> - <button className="pdfBox-nextIcon" title="Next Annotation" onClick={this.nextAnnotation} > - <FontAwesomeIcon icon={"arrow-down"} size="lg" /> + <button className="pdfBox-nextIcon" title="Next Annotation" onClick={this.nextAnnotation}> + <FontAwesomeIcon icon={'arrow-down'} size="lg" /> </button> </div> - <button className="pdfBox-overlayButton" title={searchTitle} - onClick={action(() => { this._searching = !this._searching; this.search("", true, true); })} > - <div className="pdfBox-overlayButton-arrow" onPointerDown={(e) => e.stopPropagation()} /> - <div className="pdfBox-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}> - <FontAwesomeIcon icon={this._searching ? "times" : "search"} size="lg" /> + <button + className="pdfBox-overlayButton" + title={searchTitle} + onClick={action(() => { + this._searching = !this._searching; + this.search('', true, true); + })}> + <div className="pdfBox-overlayButton-arrow" onPointerDown={e => e.stopPropagation()} /> + <div className="pdfBox-overlayButton-iconCont" onPointerDown={e => e.stopPropagation()}> + <FontAwesomeIcon icon={this._searching ? 'times' : 'search'} size="lg" /> </div> </button> <div className="pdfBox-pageNums"> - <input value={curPage} style={{ width: `${curPage > 99 ? 4 : 3}ch`, pointerEvents: "all" }} - onChange={e => this.Document._curPage = Number(e.currentTarget.value)} + <input + value={curPage} + style={{ width: `${curPage > 99 ? 4 : 3}ch`, pointerEvents: 'all' }} + onChange={e => (this.Document._curPage = Number(e.currentTarget.value))} onKeyDown={e => e.stopPropagation()} - onClick={action(() => this._pageControls = !this._pageControls)} /> - {this._pageControls ? pageBtns : (null)} + onClick={action(() => (this._pageControls = !this._pageControls))} + /> + {this._pageControls ? pageBtns : null} </div> - <div className="pdfBox-sidebarBtn" key="sidebar" title="Toggle Sidebar" + <div + className="pdfBox-sidebarBtn" + key="sidebar" + title="Toggle Sidebar" style={{ - display: !this.props.isContentActive() ? "none" : undefined, - top: StrCast(this.rootDoc._showTitle) === "title" ? 20 : 5, - backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK + display: !this.props.isContentActive() ? 'none' : undefined, + top: StrCast(this.rootDoc._showTitle) === 'title' ? 20 : 5, + backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK, }} - onPointerDown={e => this.sidebarBtnDown(e, true)} > - <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={"comment-alt"} size="sm" /> + onPointerDown={e => this.sidebarBtnDown(e, true)}> + <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" /> </div> - </div>; + </div> + ); } - sidebarWidth = () => !this.SidebarShown ? 0 : - PDFBox.sidebarResizerWidth + (this._previewWidth ? PDFBox.openSidebarWidth : - (NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() / NumCast(this.layoutDoc.nativeWidth)) + sidebarWidth = () => + !this.SidebarShown ? 0 : PDFBox.sidebarResizerWidth + (this._previewWidth ? PDFBox.openSidebarWidth : ((NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth()) / NumCast(this.layoutDoc.nativeWidth)); specificContextMenu = (e: React.MouseEvent): void => { const funcs: ContextMenuProps[] = []; - funcs.push({ description: "Copy path", event: () => this.pdfUrl && Utils.CopyText(Utils.prepend("") + this.pdfUrl.url.pathname), icon: "expand-arrows-alt" }); - funcs.push({ description: "update icon", event: () => this.pdfUrl && this.updateIcon(), icon: "expand-arrows-alt" }); + funcs.push({ description: 'Copy path', event: () => this.pdfUrl && Utils.CopyText(Utils.prepend('') + this.pdfUrl.url.pathname), icon: 'expand-arrows-alt' }); + funcs.push({ description: 'update icon', event: () => this.pdfUrl && this.updateIcon(), icon: 'expand-arrows-alt' }); //funcs.push({ description: "Toggle Sidebar ", event: () => this.toggleSidebar(), icon: "expand-arrows-alt" }); - ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" }); - } + ContextMenu.Instance.addItem({ description: 'Options...', subitems: funcs, icon: 'asterisk' }); + }; @computed get renderTitleBox() { - const classname = "pdfBox" + (this.props.isContentActive() ? "-interactive" : ""); - return <div className={classname} > - <div className="pdfBox-title-outer"> - <strong className="pdfBox-title" >{this.props.Document.title}</strong> + const classname = 'pdfBox' + (this.props.isContentActive() ? '-interactive' : ''); + return ( + <div className={classname}> + <div className="pdfBox-title-outer"> + <strong className="pdfBox-title">{StrCast(this.props.Document.title)}</strong> + </div> </div> - </div>; + ); } anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick; @observable _showSidebar = false; - @computed get SidebarShown() { return this._showSidebar || this.layoutDoc._showSidebar ? true : false; } + @computed get SidebarShown() { + return this._showSidebar || this.layoutDoc._showSidebar ? true : false; + } contentScaling = () => 1; isPdfContentActive = () => this.isAnyChildContentActive() || this.props.isSelected(); @@ -383,54 +437,60 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps TraceMobx(); const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1; const scale = previewScale * (this.props.scaling?.() || 1); - return <div className={"pdfBox"} onContextMenu={this.specificContextMenu} - style={{ - display: this.props.thumbShown?.() ? "none" : undefined, - height: this.props.Document._scrollTop && !this.Document._fitWidth && (window.screen.width > 600) ? - NumCast(this.Document._height) * this.props.PanelWidth() / NumCast(this.Document._width) : undefined - }}> - <div className="pdfBox-background" onPointerDown={e => this.sidebarBtnDown(e, false)} /> - <div style={{ - width: `calc(${100 / scale}% - ${this.sidebarWidth() / scale * (this._previewWidth ? scale : 1)}px)`, - height: `${100 / scale}%`, - transform: `scale(${scale})`, - position: "absolute", - transformOrigin: "top left", - top: 0, - }}> - <PDFViewer {...this.props} + return ( + <div + className={'pdfBox'} + onContextMenu={this.specificContextMenu} + style={{ + display: this.props.thumbShown?.() ? 'none' : undefined, + height: this.props.Document._scrollTop && !this.Document._fitWidth && window.screen.width > 600 ? (NumCast(this.Document._height) * this.props.PanelWidth()) / NumCast(this.Document._width) : undefined, + }}> + <div className="pdfBox-background" onPointerDown={e => this.sidebarBtnDown(e, false)} /> + <div + style={{ + width: `calc(${100 / scale}% - ${(this.sidebarWidth() / scale) * (this._previewWidth ? scale : 1)}px)`, + height: `${100 / scale}%`, + transform: `scale(${scale})`, + position: 'absolute', + transformOrigin: 'top left', + top: 0, + }}> + <PDFViewer + {...this.props} + rootDoc={this.rootDoc} + layoutDoc={this.layoutDoc} + dataDoc={this.dataDoc} + pdf={this._pdf!} + url={this.pdfUrl!.url.pathname} + isContentActive={this.isPdfContentActive} + anchorMenuClick={this.anchorMenuClick} + loaded={!Doc.NativeAspect(this.dataDoc) ? this.loaded : undefined} + setPdfViewer={this.setPdfViewer} + addDocument={this.addDocument} + moveDocument={this.moveDocument} + removeDocument={this.removeDocument} + whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} + crop={this.crop} + ContentScaling={returnOne} + /> + </div> + <SidebarAnnos + ref={this._sidebarRef} + {...this.props} rootDoc={this.rootDoc} layoutDoc={this.layoutDoc} dataDoc={this.dataDoc} - pdf={this._pdf!} - url={this.pdfUrl!.url.pathname} - isContentActive={this.isPdfContentActive} - anchorMenuClick={this.anchorMenuClick} - loaded={!Doc.NativeAspect(this.dataDoc) ? this.loaded : undefined} - setPdfViewer={this.setPdfViewer} - addDocument={this.addDocument} + setHeight={emptyFunction} + nativeWidth={this._previewNativeWidth ?? NumCast(this.layoutDoc._nativeWidth)} + showSidebar={this.SidebarShown} + whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} + sidebarAddDocument={this.sidebarAddDocument} moveDocument={this.moveDocument} removeDocument={this.removeDocument} - whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} - crop={this.crop} - ContentScaling={returnOne} /> + {this.settingsPanel()} </div> - <SidebarAnnos ref={this._sidebarRef} - {...this.props} - rootDoc={this.rootDoc} - layoutDoc={this.layoutDoc} - dataDoc={this.dataDoc} - setHeight={emptyFunction} - nativeWidth={this._previewNativeWidth ?? NumCast(this.layoutDoc._nativeWidth)} - showSidebar={this.SidebarShown} - whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} - sidebarAddDocument={this.sidebarAddDocument} - moveDocument={this.moveDocument} - removeDocument={this.removeDocument} - /> - {this.settingsPanel()} - </div>; + ); } static pdfcache = new Map<string, Pdfjs.PDFDocumentProxy>(); @@ -446,13 +506,12 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps const href = this.pdfUrl?.url.href; if (href) { - if (PDFBox.pdfcache.get(href)) setTimeout(action(() => this._pdf = PDFBox.pdfcache.get(href))); + if (PDFBox.pdfcache.get(href)) setTimeout(action(() => (this._pdf = PDFBox.pdfcache.get(href)))); else { if (!PDFBox.pdfpromise.get(href)) PDFBox.pdfpromise.set(href, Pdfjs.getDocument(href).promise); - PDFBox.pdfpromise.get(href)?.then(action((pdf: any) => PDFBox.pdfcache.set(href, this._pdf = pdf))); + PDFBox.pdfpromise.get(href)?.then(action((pdf: any) => PDFBox.pdfcache.set(href, (this._pdf = pdf)))); } } return this.renderTitleBox; } } - diff --git a/src/client/views/nodes/formattedText/schema_rts.ts b/src/client/views/nodes/formattedText/schema_rts.ts index 83561073c..d6e0e6002 100644 --- a/src/client/views/nodes/formattedText/schema_rts.ts +++ b/src/client/views/nodes/formattedText/schema_rts.ts @@ -1,8 +1,7 @@ -import { Schema, Slice } from "prosemirror-model"; - -import { nodes } from "./nodes_rts"; -import { marks } from "./marks_rts"; +import { Schema, Slice } from 'prosemirror-model'; +import { nodes } from './nodes_rts'; +import { marks } from './marks_rts'; // :: Schema // This schema rougly corresponds to the document schema used by @@ -20,7 +19,9 @@ const fromJson = schema.nodeFromJSON; schema.nodeFromJSON = (json: any) => { const node = fromJson(json); if (json.type === schema.nodes.summary.name) { - node.attrs.text = Slice.fromJSON(schema, node.attrs.textslice); + // bcz: this is a hacky way to convert the JSON that's serialized for a summary node into the Slice that the summary node wants at run-time. + // since attrs are readonly, assigning the text field like this violates the way prosemirror works, but I think we can get away with it. + (node.attrs.text as any) = Slice.fromJSON(schema, node.attrs.textslice); } return node; -};
\ No newline at end of file +}; diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 7045d6d5d..91f8d1efc 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -1,55 +1,57 @@ -import React = require("react"); -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Tooltip } from "@material-ui/core"; -import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import { ColorState, SketchPicker } from "react-color"; +import React = require('react'); +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Tooltip } from '@material-ui/core'; +import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import { ColorState, SketchPicker } from 'react-color'; import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal'; -import { Doc, DocListCast, DocListCastAsync, FieldResult } from "../../../../fields/Doc"; -import { InkTool } from "../../../../fields/InkField"; -import { List } from "../../../../fields/List"; -import { PrefetchProxy } from "../../../../fields/Proxy"; -import { listSpec } from "../../../../fields/Schema"; -import { BoolCast, Cast, DocCast, NumCast, StrCast } from "../../../../fields/Types"; +import { Doc, DocListCast, DocListCastAsync, FieldResult } from '../../../../fields/Doc'; +import { InkTool } from '../../../../fields/InkField'; +import { List } from '../../../../fields/List'; +import { PrefetchProxy } from '../../../../fields/Proxy'; +import { listSpec } from '../../../../fields/Schema'; +import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types'; import { emptyFunction, returnFalse, returnOne, returnTrue, setupMoveUpEvents } from '../../../../Utils'; -import { Docs } from "../../../documents/Documents"; -import { DocumentType } from "../../../documents/DocumentTypes"; -import { CurrentUserUtils } from "../../../util/CurrentUserUtils"; -import { DocumentManager } from "../../../util/DocumentManager"; -import { SelectionManager } from "../../../util/SelectionManager"; -import { undoBatch, UndoManager } from "../../../util/UndoManager"; -import { CollectionDockingView } from "../../collections/CollectionDockingView"; -import { MarqueeViewBounds } from "../../collections/collectionFreeForm"; -import { CollectionView, CollectionViewType } from "../../collections/CollectionView"; -import { TabDocView } from "../../collections/TabDocView"; -import { ViewBoxBaseComponent } from "../../DocComponent"; -import { Colors } from "../../global/globalEnums"; -import { LightboxView } from "../../LightboxView"; -import { CollectionFreeFormDocumentView } from "../CollectionFreeFormDocumentView"; +import { Docs } from '../../../documents/Documents'; +import { DocumentType } from '../../../documents/DocumentTypes'; +import { CurrentUserUtils } from '../../../util/CurrentUserUtils'; +import { DocumentManager } from '../../../util/DocumentManager'; +import { SelectionManager } from '../../../util/SelectionManager'; +import { undoBatch, UndoManager } from '../../../util/UndoManager'; +import { CollectionDockingView } from '../../collections/CollectionDockingView'; +import { MarqueeViewBounds } from '../../collections/collectionFreeForm'; +import { CollectionView, CollectionViewType } from '../../collections/CollectionView'; +import { TabDocView } from '../../collections/TabDocView'; +import { ViewBoxBaseComponent } from '../../DocComponent'; +import { Colors } from '../../global/globalEnums'; +import { LightboxView } from '../../LightboxView'; +import { CollectionFreeFormDocumentView } from '../CollectionFreeFormDocumentView'; import { FieldView, FieldViewProps } from '../FieldView'; -import "./PresBox.scss"; -import { PresEffect, PresMovement, PresStatus } from "./PresEnums"; -import { ScriptingGlobals } from "../../../util/ScriptingGlobals"; -import { PresElementBox } from "."; +import './PresBox.scss'; +import { PresEffect, PresMovement, PresStatus } from './PresEnums'; +import { ScriptingGlobals } from '../../../util/ScriptingGlobals'; +import { PresElementBox } from '.'; export interface PinProps { audioRange?: boolean; setPosition?: boolean; hidePresBox?: boolean; pinWithView?: PinViewProps; - pinDocView?: boolean; // whether the current view specs of the document should be saved the pinned document - panelWidth?: number; // panel width and height of the document (used to compute the bounds of the pinned view area) - panelHeight?: number + pinDocView?: boolean; // whether the current view specs of the document should be saved the pinned document + panelWidth?: number; // panel width and height of the document (used to compute the bounds of the pinned view area) + panelHeight?: number; } export interface PinViewProps { - bounds: MarqueeViewBounds; - scale: number; + bounds: MarqueeViewBounds; + scale: number; } @observer export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresBox, fieldKey); } + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(PresBox, fieldKey); + } /** * transitions & effects for documents @@ -67,22 +69,27 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { // when: this.layoutDoc === PresBox.Instance.childDocs[PresBox.Instance.itemIndex]?.presentationTargetDoc, }; switch (presDoc.presEffect) { - case PresEffect.Zoom: return (<Zoom {...effectProps}>{renderDoc}</Zoom>); - case PresEffect.Fade: return (<Fade {...effectProps}>{renderDoc}</Fade>); - case PresEffect.Flip: return (<Flip {...effectProps}>{renderDoc}</Flip>); - case PresEffect.Rotate: return (<Rotate {...effectProps}>{renderDoc}</Rotate>); - case PresEffect.Bounce: return (<Bounce {...effectProps}>{renderDoc}</Bounce>); - case PresEffect.Roll: return (<Roll {...effectProps}>{renderDoc}</Roll>); - case PresEffect.Lightspeed: return (<LightSpeed {...effectProps}>{renderDoc}</LightSpeed>); + case PresEffect.Zoom: + return <Zoom {...effectProps}>{renderDoc}</Zoom>; + case PresEffect.Fade: + return <Fade {...effectProps}>{renderDoc}</Fade>; + case PresEffect.Flip: + return <Flip {...effectProps}>{renderDoc}</Flip>; + case PresEffect.Rotate: + return <Rotate {...effectProps}>{renderDoc}</Rotate>; + case PresEffect.Bounce: + return <Bounce {...effectProps}>{renderDoc}</Bounce>; + case PresEffect.Roll: + return <Roll {...effectProps}>{renderDoc}</Roll>; + case PresEffect.Lightspeed: + return <LightSpeed {...effectProps}>{renderDoc}</LightSpeed>; case PresEffect.None: - default: return renderDoc; + default: + return renderDoc; } } public static EffectsProvider(layoutDoc: Doc, renderDoc: any) { - return PresBox.Instance && layoutDoc === PresBox.Instance.childDocs[PresBox.Instance.itemIndex]?.presentationTargetDoc ? - PresBox.renderEffectsDoc(renderDoc, layoutDoc, PresBox.Instance.childDocs[PresBox.Instance.itemIndex]) - : - renderDoc; + return PresBox.Instance && layoutDoc === PresBox.Instance.childDocs[PresBox.Instance.itemIndex]?.presentationTargetDoc ? PresBox.renderEffectsDoc(renderDoc, layoutDoc, PresBox.Instance.childDocs[PresBox.Instance.itemIndex]) : renderDoc; } @observable public static Instance: PresBox; @@ -107,10 +114,18 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { @observable private openMovementDropdown: boolean = false; @observable private openEffectDropdown: boolean = false; @observable private presentTools: boolean = false; - @computed get isTreeOrStack() {return [CollectionViewType.Tree, CollectionViewType.Stacking].includes(StrCast(this.layoutDoc._viewType) as any) } - @computed get isTree() { return this.layoutDoc._viewType === CollectionViewType.Tree;} - @computed get presFieldKey() { return StrCast(this.layoutDoc.presFieldKey, "data"); } - @computed get childDocs() { return DocListCast(this.rootDoc[this.presFieldKey]); } + @computed get isTreeOrStack() { + return [CollectionViewType.Tree, CollectionViewType.Stacking].includes(StrCast(this.layoutDoc._viewType) as any); + } + @computed get isTree() { + return this.layoutDoc._viewType === CollectionViewType.Tree; + } + @computed get presFieldKey() { + return StrCast(this.layoutDoc.presFieldKey, 'data'); + } + @computed get childDocs() { + return DocListCast(this.rootDoc[this.presFieldKey]); + } @observable _treeViewMap: Map<Doc, number> = new Map(); @computed get tagDocs() { @@ -121,9 +136,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { } return tagDocs; } - @computed get itemIndex() { return NumCast(this.rootDoc._itemIndex); } - @computed get activeItem() { return Cast(this.childDocs[NumCast(this.rootDoc._itemIndex)], Doc, null); } - @computed get targetDoc() { return Cast(this.activeItem?.presentationTargetDoc, Doc, null); } + @computed get itemIndex() { + return NumCast(this.rootDoc._itemIndex); + } + @computed get activeItem() { + return Cast(this.childDocs[NumCast(this.rootDoc._itemIndex)], Doc, null); + } + @computed get targetDoc() { + return Cast(this.activeItem?.presentationTargetDoc, Doc, null); + } @computed get scrollable(): boolean { if (this.targetDoc.type === DocumentType.PDF || this.targetDoc.type === DocumentType.WEB || this.targetDoc.type === DocumentType.RTF || this.targetDoc._viewType === CollectionViewType.Stacking) return true; else return false; @@ -134,7 +155,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { } constructor(props: any) { super(props); - if (CurrentUserUtils.ActivePresentation = this.rootDoc) runInAction(() => PresBox.Instance = this); + if ((CurrentUserUtils.ActivePresentation = this.rootDoc)) runInAction(() => (PresBox.Instance = this)); this.props.Document.presentationFieldKey = this.fieldKey; // provide info to the presElement script so that it can look up rendering information about the presBox } @computed get selectedDocumentView() { @@ -142,21 +163,23 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { if (this._selectedArray.size) return DocumentManager.Instance.getDocumentView(this.rootDoc); } @computed get isPres(): boolean { - document.removeEventListener("keydown", PresBox.keyEventsWrapper, true); + document.removeEventListener('keydown', PresBox.keyEventsWrapper, true); if (this.selectedDoc?.type === DocumentType.PRES) { - document.removeEventListener("keydown", PresBox.keyEventsWrapper, true); - document.addEventListener("keydown", PresBox.keyEventsWrapper, true); + document.removeEventListener('keydown', PresBox.keyEventsWrapper, true); + document.addEventListener('keydown', PresBox.keyEventsWrapper, true); return true; } return false; } - @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; } + @computed get selectedDoc() { + return this.selectedDocumentView?.rootDoc; + } _unmounting = false; @action componentWillUnmount() { this._unmounting = true; - document.removeEventListener("keydown", PresBox.keyEventsWrapper, true); + document.removeEventListener('keydown', PresBox.keyEventsWrapper, true); this._presKeyEventsActive = false; this.resetPresentation(); // Turn of progressivize editors @@ -167,37 +190,38 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { @action componentDidMount() { this._unmounting = false; - this.rootDoc._forceRenderEngine = "timeline"; + this.rootDoc._forceRenderEngine = 'timeline'; this.layoutDoc.presStatus = PresStatus.Edit; this.layoutDoc._gridGap = 0; this.layoutDoc._yMargin = 0; this.turnOffEdit(true); - DocListCastAsync(CurrentUserUtils.MyTrails.data).then(pres => - !pres?.includes(this.rootDoc) && Doc.AddDocToList(CurrentUserUtils.MyTrails, "data", this.rootDoc)); - this._disposers.selection = reaction(() => SelectionManager.Views(), - views => views.some(view => view.props.Document === this.rootDoc) && this.updateCurrentPresentation()); + DocListCastAsync(CurrentUserUtils.MyTrails.data).then(pres => !pres?.includes(this.rootDoc) && Doc.AddDocToList(CurrentUserUtils.MyTrails, 'data', this.rootDoc)); + this._disposers.selection = reaction( + () => SelectionManager.Views(), + views => views.some(view => view.props.Document === this.rootDoc) && this.updateCurrentPresentation() + ); } @action updateCurrentPresentation = (pres?: Doc) => { if (pres) CurrentUserUtils.ActivePresentation = pres; else CurrentUserUtils.ActivePresentation = this.rootDoc; - document.removeEventListener("keydown", PresBox.keyEventsWrapper, true); - document.addEventListener("keydown", PresBox.keyEventsWrapper, true); + document.removeEventListener('keydown', PresBox.keyEventsWrapper, true); + document.addEventListener('keydown', PresBox.keyEventsWrapper, true); this._presKeyEventsActive = true; PresBox.Instance = this; - } + }; // There are still other internal frames and should go through all frames before going to next slide nextInternalFrame = (targetDoc: Doc, activeItem: Doc) => { - const currentFrame = Cast(targetDoc?._currentFrame, "number", null); + const currentFrame = Cast(targetDoc?._currentFrame, 'number', null); const childDocs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]); - targetDoc._viewTransition = "all 1s"; - setTimeout(() => targetDoc._viewTransition = undefined, 1010); + targetDoc._viewTransition = 'all 1s'; + setTimeout(() => (targetDoc._viewTransition = undefined), 1010); this.nextKeyframe(targetDoc, activeItem); if (activeItem.presProgressivize) CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0, targetDoc); else targetDoc.keyFrameEditing = true; - } + }; _mediaTimer!: [NodeJS.Timeout, Doc]; // 'Play on next' for audio or video therefore first navigate to the audio/video before it should be played @@ -207,7 +231,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const targMedia = DocumentManager.Instance.getDocumentView(targetDoc); targMedia?.ComponentView?.playFrom?.(NumCast(activeItem.presStartTime), NumCast(activeItem.presStartTime) + duration); } - } + }; stopTempMedia = (targetDocField: FieldResult) => { const targetDoc = Cast(targetDocField, Doc, null); @@ -215,11 +239,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const targMedia = DocumentManager.Instance.getDocumentView(targetDoc); targMedia?.ComponentView?.Pause?.(); } - } + }; - //TODO: al: it seems currently that tempMedia doesn't stop onslidechange after clicking the button; the time the tempmedia stop depends on the start & end time + //TODO: al: it seems currently that tempMedia doesn't stop onslidechange after clicking the button; the time the tempmedia stop depends on the start & end time // TODO: to handle child slides (entering into subtrail and exiting), also the next() and back() functions - // No more frames in current doc and next slide is defined, therefore move to next slide + // No more frames in current doc and next slide is defined, therefore move to next slide nextSlide = (activeNext: Doc) => { const targetNext = Cast(activeNext.presentationTargetDoc, Doc, null); console.info('nextSlide', activeNext.title, targetNext?.title); @@ -229,11 +253,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { if (!this.childDocs[nextSelected].groupWithUp) { break; } else { - console.log("Title: " + this.childDocs[nextSelected].title); + console.log('Title: ' + this.childDocs[nextSelected].title); this.gotoDocument(nextSelected, this.activeItem, true); } } - } + }; // Called when the user activates 'next' - to move to the next part of the pres. trail @action @@ -241,7 +265,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const activeNext = Cast(this.childDocs[this.itemIndex + 1], Doc, null); const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; - const lastFrame = Cast(targetDoc?.lastFrame, "number", null); + const lastFrame = Cast(targetDoc?.lastFrame, 'number', null); const curFrame = NumCast(targetDoc?._currentFrame); let internalFrames: boolean = false; if (activeItem.presProgressivize || activeItem.zoomProgressivize || targetDoc.scrollProgressivize) internalFrames = true; @@ -249,13 +273,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { // Case 1: There are still other frames and should go through all frames before going to next slide this.nextInternalFrame(targetDoc, activeItem); } else if (this.childDocs[this.itemIndex + 1] !== undefined) { - // Case 2: No more frames in current doc and next slide is defined, therefore move to next slide + // Case 2: No more frames in current doc and next slide is defined, therefore move to next slide this.nextSlide(activeNext); } else if (this.childDocs[this.itemIndex + 1] === undefined && (this.layoutDoc.presLoop || this.layoutDoc.presStatus === PresStatus.Edit)) { // Case 3: Last slide and presLoop is toggled ON or it is in Edit mode this.gotoDocument(0, this.activeItem); } - } + }; // Called when the user activates 'back' - to move to the previous part of the pres. trail @action @@ -264,7 +288,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const targetDoc: Doc = this.targetDoc; const prevItem = Cast(this.childDocs[Math.max(0, this.itemIndex - 1)], Doc, null); const prevTargetDoc = Cast(prevItem.presentationTargetDoc, Doc, null); - const lastFrame = Cast(targetDoc.lastFrame, "number", null); + const lastFrame = Cast(targetDoc.lastFrame, 'number', null); const curFrame = NumCast(targetDoc._currentFrame); let prevSelected = this.itemIndex; // Functionality for group with up @@ -284,7 +308,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { // Case 3: Pres loop is on so it should go to the last slide this.gotoDocument(this.childDocs.length - 1, activeItem); } - } + }; //The function that is called when a document is clicked or reached through next or back. //it'll also execute the necessary actions if presentation is playing. @@ -297,20 +321,21 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { if (from?.mediaStopTriggerList && this.layoutDoc.presStatus !== PresStatus.Edit) { DocListCast(from.mediaStopTriggerList).forEach(this.stopTempMedia); } - if (from?.mediaStop === "auto" && this.layoutDoc.presStatus !== PresStatus.Edit) { + if (from?.mediaStop === 'auto' && this.layoutDoc.presStatus !== PresStatus.Edit) { this.stopTempMedia(from.presentationTargetDoc); } // If next slide is audio / video 'Play automatically' then the next slide should be played - if (this.layoutDoc.presStatus !== PresStatus.Edit && (targetDoc.type === DocumentType.AUDIO || targetDoc.type === DocumentType.VID) && (activeItem.mediaStart === "auto")) { + if (this.layoutDoc.presStatus !== PresStatus.Edit && (targetDoc.type === DocumentType.AUDIO || targetDoc.type === DocumentType.VID) && activeItem.mediaStart === 'auto') { this.startTempMedia(targetDoc, activeItem); } if (targetDoc) { - Doc.linkFollowHighlight((targetDoc.annotationOn instanceof Doc) ? [targetDoc, targetDoc.annotationOn] : targetDoc); - targetDoc && runInAction(() => { - if (activeItem.presMovement === PresMovement.Jump) targetDoc.focusSpeed = 0; - else targetDoc.focusSpeed = activeItem.presTransition ? activeItem.presTransition : 500; - }); - setTimeout(() => targetDoc.focusSpeed = 500, this.activeItem.presTransition ? NumCast(this.activeItem.presTransition) + 10 : 510); + Doc.linkFollowHighlight(targetDoc.annotationOn instanceof Doc ? [targetDoc, targetDoc.annotationOn] : targetDoc); + targetDoc && + runInAction(() => { + if (activeItem.presMovement === PresMovement.Jump) targetDoc.focusSpeed = 0; + else targetDoc.focusSpeed = activeItem.presTransition ? activeItem.presTransition : 500; + }); + setTimeout(() => (targetDoc.focusSpeed = 500), this.activeItem.presTransition ? NumCast(this.activeItem.presTransition) + 10 : 510); } if (targetDoc?.lastFrame !== undefined) { targetDoc._currentFrame = 0; @@ -327,14 +352,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { clearTimeout(this._navTimer); const bestTarget = DocumentManager.Instance.getFirstDocumentView(targetDoc)?.props.Document; if (bestTarget) console.log(bestTarget.title, bestTarget.type); - else console.log("no best target"); - if (bestTarget) this._navTimer = PresBox.navigateToDoc(bestTarget, activeItem, false); - } + else console.log('no best target'); + if (bestTarget) this._navTimer = PresBox.navigateToDoc(bestTarget, activeItem, false); + }; // navigates to the bestTarget document by making sure it is on screen, - // then it applies the view specs stored in activeItem to + // then it applies the view specs stored in activeItem to @action - static navigateToDoc(bestTarget:Doc, activeItem:Doc, jumpToDoc:boolean) { + static navigateToDoc(bestTarget: Doc, activeItem: Doc, jumpToDoc: boolean) { if (bestTarget.type === DocumentType.PDF || bestTarget.type === DocumentType.WEB || bestTarget.type === DocumentType.RTF || bestTarget._viewType === CollectionViewType.Stacking) { bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s'; bestTarget._scrollTop = activeItem.presPinViewScroll; @@ -343,32 +368,31 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { } else if ([DocumentType.AUDIO, DocumentType.VID].includes(bestTarget.type as any)) { bestTarget._currentTimecode = activeItem.presStartTime; } else { - const contentBounds= Cast(activeItem.contentBounds, listSpec("number")); + const contentBounds = Cast(activeItem.contentBounds, listSpec('number')); bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s'; if (contentBounds) { - bestTarget._panX = (contentBounds[0] + contentBounds[2])/2; - bestTarget._panY = (contentBounds[1] + contentBounds[3])/2; + bestTarget._panX = (contentBounds[0] + contentBounds[2]) / 2; + bestTarget._panY = (contentBounds[1] + contentBounds[3]) / 2; const dv = DocumentManager.Instance.getDocumentView(bestTarget); if (dv) { - bestTarget._viewScale = Math.min(dv.props.PanelHeight() / (contentBounds[3] - contentBounds[1]), - dv.props.PanelWidth() / (contentBounds[2]- contentBounds[0])); - }; + bestTarget._viewScale = Math.min(dv.props.PanelHeight() / (contentBounds[3] - contentBounds[1]), dv.props.PanelWidth() / (contentBounds[2] - contentBounds[0])); + } } else { bestTarget._panX = activeItem.presPinViewX; bestTarget._panY = activeItem.presPinViewY; bestTarget._viewScale = activeItem.presPinViewScale; } } - return setTimeout(() => bestTarget._viewTransition = undefined, activeItem.presTransition ? NumCast(activeItem.presTransition) + 10 : 510); + return setTimeout(() => (bestTarget._viewTransition = undefined), activeItem.presTransition ? NumCast(activeItem.presTransition) + 10 : 510); } /** * This method makes sure that cursor navigates to the element that - * has the option open and last in the group. - * Design choice: If the next document is not in presCollection or + * has the option open and last in the group. + * Design choice: If the next document is not in presCollection or * presCollection itself then if there is a presCollection it will add * a new tab. If presCollection is undefined it will open the document - * on the right. + * on the right. */ navigateToElement = async (curDoc: Doc) => { const activeItem: Doc = this.activeItem; @@ -401,7 +425,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { self._eleArray.splice(0, self._eleArray.length, ...eleViewCache); }); const openInTab = (doc: Doc, finished?: () => void) => { - collectionDocView ? collectionDocView.props.addDocTab(doc, "") : this.props.addDocTab(doc, ""); + collectionDocView ? collectionDocView.props.addDocTab(doc, '') : this.props.addDocTab(doc, ''); this.layoutDoc.presCollection = targetDoc; // this still needs some fixing setTimeout(resetSelection, 500); @@ -417,21 +441,21 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { // openInTab(targetDoc); } else if (curDoc.presMovement === PresMovement.Pan && targetDoc) { LightboxView.SetLightboxDoc(undefined); - await DocumentManager.Instance.jumpToDocument(targetDoc, false, openInTab, srcContext ? [srcContext]:[], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true); // documents open in new tab instead of on right + await DocumentManager.Instance.jumpToDocument(targetDoc, false, openInTab, srcContext ? [srcContext] : [], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true); // documents open in new tab instead of on right } else if ((curDoc.presMovement === PresMovement.Zoom || curDoc.presMovement === PresMovement.Jump) && targetDoc) { LightboxView.SetLightboxDoc(undefined); - //awaiting jump so that new scale can be found, since jumping is async - await DocumentManager.Instance.jumpToDocument(targetDoc, true, openInTab, srcContext ? [srcContext]:[], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true, NumCast(curDoc.presZoom)); // documents open in new tab instead of on right + //awaiting jump so that new scale can be found, since jumping is async + await DocumentManager.Instance.jumpToDocument(targetDoc, true, openInTab, srcContext ? [srcContext] : [], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true, NumCast(curDoc.presZoom)); // documents open in new tab instead of on right } // After navigating to the document, if it is added as a presPinView then it will // adjust the pan and scale to that of the pinView when it was added. if (activeItem.presPinView) { console.log(targetDoc.title); - console.log("presPinView in PresBox.tsx:420"); + console.log('presPinView in PresBox.tsx:420'); // if targetDoc is not displayed but one of its aliases is, then we need to modify that alias, not the original target this.navigateToView(targetDoc, activeItem); } - } + }; /** * Uses the viewfinder to progressivize through the different views of a single collection. @@ -441,10 +465,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const targetDoc: Doc = this.targetDoc; const srcContext = Cast(targetDoc?.context, Doc, null); const docView = DocumentManager.Instance.getDocumentView(targetDoc); - const vfLeft = this.checkList(targetDoc, activeItem["viewfinder-left-indexed"]); - const vfWidth = this.checkList(targetDoc, activeItem["viewfinder-width-indexed"]); - const vfTop = this.checkList(targetDoc, activeItem["viewfinder-top-indexed"]); - const vfHeight = this.checkList(targetDoc, activeItem["viewfinder-height-indexed"]); + const vfLeft = this.checkList(targetDoc, activeItem['viewfinder-left-indexed']); + const vfWidth = this.checkList(targetDoc, activeItem['viewfinder-width-indexed']); + const vfTop = this.checkList(targetDoc, activeItem['viewfinder-top-indexed']); + const vfHeight = this.checkList(targetDoc, activeItem['viewfinder-height-indexed']); // Case 1: document that is not a Golden Layout tab if (srcContext) { const srcDocView = DocumentManager.Instance.getDocumentView(srcContext); @@ -455,8 +479,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const newPanX = NumCast(targetDoc.x) + NumCast(layoutdoc._width) / 2; const newPanY = NumCast(targetDoc.y) + NumCast(layoutdoc._height) / 2; const newScale = 0.9 * Math.min(Number(panelWidth) / vfWidth, Number(panelHeight) / vfHeight); - srcContext._panX = newPanX + (vfLeft + (vfWidth / 2)); - srcContext._panY = newPanY + (vfTop + (vfHeight / 2)); + srcContext._panX = newPanX + (vfLeft + vfWidth / 2); + srcContext._panY = newPanY + (vfTop + vfHeight / 2); srcContext._viewScale = newScale; } } @@ -465,8 +489,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const panelWidth: number = docView.props.PanelWidth(); const panelHeight: number = docView.props.PanelHeight(); const newScale = 0.9 * Math.min(Number(panelWidth) / vfWidth, Number(panelHeight) / vfHeight); - targetDoc._panX = vfLeft + (vfWidth / 2); - targetDoc._panY = vfTop + (vfWidth / 2); + targetDoc._panX = vfLeft + vfWidth / 2; + targetDoc._panY = vfTop + vfWidth / 2; targetDoc._viewScale = newScale; } const resize = document.getElementById('resizable'); @@ -476,7 +500,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { resize.style.top = vfTop + 'px'; resize.style.left = vfLeft + 'px'; } - } + }; /** * For 'Hide Before' and 'Hide After' buttons making sure that @@ -490,18 +514,19 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { if (tagDoc) tagDoc.opacity = 1; const itemIndexes: number[] = this.getAllIndexes(this.tagDocs, tagDoc); const curInd: number = itemIndexes.indexOf(index); - if (tagDoc === this.layoutDoc.presCollection) { tagDoc.opacity = 1; } - else { - if (itemIndexes.length > 1 && curDoc.presHideBefore && curInd !== 0) { } - else if (curDoc.presHideBefore) { + if (tagDoc === this.layoutDoc.presCollection) { + tagDoc.opacity = 1; + } else { + if (itemIndexes.length > 1 && curDoc.presHideBefore && curInd !== 0) { + } else if (curDoc.presHideBefore) { if (index > this.itemIndex) { tagDoc.opacity = 0; } else if (!curDoc.presHideAfter) { tagDoc.opacity = 1; } } - if (itemIndexes.length > 1 && curDoc.presHideAfter && curInd !== (itemIndexes.length - 1)) { } - else if (curDoc.presHideAfter) { + if (itemIndexes.length > 1 && curDoc.presHideAfter && curInd !== itemIndexes.length - 1) { + } else if (curDoc.presHideAfter) { if (index < this.itemIndex) { tagDoc.opacity = 0; } else if (!curDoc.presHideBefore) { @@ -510,9 +535,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { } } }); - } - - + }; //The function that starts or resets presentaton functionally, depending on presStatus of the layoutDoc @action @@ -521,13 +544,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { let activeItem: Doc = this.activeItem; let targetDoc: Doc = this.targetDoc; let duration = NumCast(activeItem.presDuration) + NumCast(activeItem.presTransition); - const timer = (ms: number) => new Promise(res => this._presTimer = setTimeout(res, ms)); - const load = async () => { // Wrap the loop into an async function for this to work + const timer = (ms: number) => new Promise(res => (this._presTimer = setTimeout(res, ms))); + const load = async () => { + // Wrap the loop into an async function for this to work for (var i = startSlide; i < this.childDocs.length; i++) { activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); duration = NumCast(activeItem.presDuration) + NumCast(activeItem.presTransition); - if (duration < 100) { duration = 2500; } + if (duration < 100) { + duration = 2500; + } if (NumCast(targetDoc.lastFrame) > 0) { for (var f = 0; f < NumCast(targetDoc.lastFrame); f++) { await timer(duration / NumCast(targetDoc.lastFrame)); @@ -535,7 +561,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { } } - await timer(duration); this.next(); // then the created Promise can be awaited + await timer(duration); + this.next(); // then the created Promise can be awaited if (i === this.childDocs.length - 1) { setTimeout(() => { clearTimeout(this._presTimer); @@ -549,7 +576,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { this.startPresentation(startSlide); this.gotoDocument(startSlide, activeItem); load(); - } + }; // The function pauses the auto presentation @action @@ -560,20 +587,23 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { this.layoutDoc.presLoop = false; this.childDocs.forEach(this.stopTempMedia); } - } + }; //The function that resets the presentation by removing every action done by it. It also //stops the presentaton. resetPresentation = () => { this.rootDoc._itemIndex = 0; - this.childDocs.map(doc => Cast(doc.presentationTargetDoc, Doc, null)).filter(doc => doc instanceof Doc).forEach(doc => { - try { - doc.opacity = 1; - } catch (e) { - console.log("Reset presentation error: ", e); - } - }); - } + this.childDocs + .map(doc => Cast(doc.presentationTargetDoc, Doc, null)) + .filter(doc => doc instanceof Doc) + .forEach(doc => { + try { + doc.opacity = 1; + } catch (e) { + console.log('Reset presentation error: ', e); + } + }); + }; // The function allows for viewing the pres path on toggle @action togglePath = (srcContext: Doc, off?: boolean) => { @@ -581,19 +611,19 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { this._pathBoolean = false; srcContext.presPathView = false; } else { - runInAction(() => this._pathBoolean = !this._pathBoolean); + runInAction(() => (this._pathBoolean = !this._pathBoolean)); srcContext.presPathView = this._pathBoolean; } - } + }; // The function allows for expanding the view of pres on toggle @action toggleExpandMode = () => { - runInAction(() => this._expandBoolean = !this._expandBoolean); + runInAction(() => (this._expandBoolean = !this._expandBoolean)); this.rootDoc.expandBoolean = this._expandBoolean; - this.childDocs.forEach((doc) => { + this.childDocs.forEach(doc => { doc.presExpandInlineButton = this._expandBoolean; }); - } + }; /** * The function that starts the presentation at the given index, also checking if actions should be applied @@ -611,7 +641,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { tagDoc.opacity = 0; } }); - } + }; /** * The method called to open the presentation as a minimized view @@ -621,7 +651,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { if (DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.layoutDoc)) { this.layoutDoc.presStatus = PresStatus.Edit; Doc.RemoveDocFromList(CurrentUserUtils.MyOverlayDocs, undefined, this.rootDoc); - CollectionDockingView.AddSplit(this.rootDoc, "right"); + CollectionDockingView.AddSplit(this.rootDoc, 'right'); } else { this.layoutDoc.presStatus = PresStatus.Edit; clearTimeout(this._presTimer); @@ -633,7 +663,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { Doc.AddDocToList(CurrentUserUtils.MyOverlayDocs, undefined, this.rootDoc); this.props.removeDocument?.(this.layoutDoc); } - } + }; /** * Called when the user changes the view type @@ -643,7 +673,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { viewChanged = action((e: React.ChangeEvent) => { //@ts-ignore const viewType = e.target.selectedOptions[0].value as CollectionViewType; - this.layoutDoc.presFieldKey = this.fieldKey+(viewType === CollectionViewType.Tree ?"-linearized":""); + this.layoutDoc.presFieldKey = this.fieldKey + (viewType === CollectionViewType.Tree ? '-linearized' : ''); // pivot field may be set by the user in timeline view (or some other way) -- need to reset it here [CollectionViewType.Tree || CollectionViewType.Stacking].includes(viewType) && (this.rootDoc._pivotField = undefined); this.rootDoc._viewType = viewType; @@ -677,20 +707,30 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { } }); - setMovementName = action((movement: any, activeItem: Doc): string => { let output: string = 'none'; switch (movement) { - case PresMovement.Zoom: output = 'Pan & Zoom'; break; //Pan and zoom - case PresMovement.Pan: output = 'Pan'; break; //Pan - case PresMovement.Jump: output = 'Jump cut'; break; //Jump Cut - case PresMovement.None: output = 'None'; break; //None - default: output = 'Zoom'; activeItem.presMovement = 'zoom'; break; //default set as zoom + case PresMovement.Zoom: + output = 'Pan & Zoom'; + break; //Pan and zoom + case PresMovement.Pan: + output = 'Pan'; + break; //Pan + case PresMovement.Jump: + output = 'Jump cut'; + break; //Jump Cut + case PresMovement.None: + output = 'None'; + break; //None + default: + output = 'Zoom'; + activeItem.presMovement = 'zoom'; + break; //default set as zoom } return output; }); - whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged(this._isChildActive = isActive)); + whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged((this._isChildActive = isActive))); // For dragging documents into the presentation trail addDocumentFilter = (docs: Doc[]) => { docs.forEach((doc, i) => { @@ -698,8 +738,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { if (doc.type === DocumentType.LABEL) { const audio = Cast(doc.annotationOn, Doc, null); if (audio) { - audio.mediaStart = "manual"; - audio.mediaStop = "manual"; + audio.mediaStart = 'manual'; + audio.mediaStop = 'manual'; audio.presStartTime = NumCast(doc._timecodeToShow /* audioStart */, NumCast(doc._timecodeToShow /* videoStart */)); audio.presEndTime = NumCast(doc._timecodeToHide /* audioEnd */, NumCast(doc._timecodeToHide /* videoEnd */)); audio.presDuration = audio.presStartTime - audio.presEndTime; @@ -714,7 +754,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { setTimeout(() => this.removeDocument(doc), 0); return false; } else { - if (!doc.presentationTargetDoc) doc.title = doc.title + " - Slide"; + if (!doc.presentationTargetDoc) doc.title = doc.title + ' - Slide'; doc.aliasOf instanceof Doc && (doc.presentationTargetDoc = doc.aliasOf); doc.presMovement = PresMovement.Zoom; if (this._expandBoolean) doc.presExpandInlineButton = true; @@ -722,13 +762,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { } }); return true; - } - childLayoutTemplate = () => !this.isTreeOrStack ? undefined : DocCast(Doc.UserDoc().presElement); + }; + childLayoutTemplate = () => (!this.isTreeOrStack ? undefined : DocCast(Doc.UserDoc().presElement)); removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.rootDoc, this.fieldKey, doc); - getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight + getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65); // listBox padding-left and pres-box-cont minHeight panelHeight = () => this.props.PanelHeight() - 40; - isContentActive = (outsideReaction?: boolean) => ((CurrentUserUtils.ActiveTool === InkTool.None && !this.layoutDoc._lockedPosition) && - (this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false) + isContentActive = (outsideReaction?: boolean) => + CurrentUserUtils.ActiveTool === InkTool.None && !this.layoutDoc._lockedPosition && (this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false; /** * For sorting the array so that the order is maintained when it is dropped. @@ -736,7 +776,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { @action sortArray = (): Doc[] => { return this.childDocs.filter(doc => this._selectedArray.has(doc)); - } + }; /** * Method to get the list of selected items in the order in which they have been selected @@ -745,9 +785,26 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const list = Array.from(this._selectedArray.keys()).map((doc: Doc, index: any) => { const curDoc = Cast(doc, Doc, null); const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null); - if (curDoc && curDoc === this.activeItem) return <div key={index} className="selectedList-items"><b>{index + 1}. {curDoc.title}</b></div>; - else if (tagDoc) return <div key={index} className="selectedList-items">{index + 1}. {curDoc.title}</div>; - else if (curDoc) return <div key={index} className="selectedList-items">{index + 1}. {curDoc.title}</div>; + if (curDoc && curDoc === this.activeItem) + return ( + <div key={index} className="selectedList-items"> + <b> + {index + 1}. {curDoc.title} + </b> + </div> + ); + else if (tagDoc) + return ( + <div key={index} className="selectedList-items"> + {index + 1}. {curDoc.title} + </div> + ); + else if (curDoc) + return ( + <div key={index} className="selectedList-items"> + {index + 1}. {curDoc.title} + </div> + ); }); return list; } @@ -756,7 +813,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { selectPres = () => { const presDocView = DocumentManager.Instance.getDocumentView(this.rootDoc); presDocView && SelectionManager.SelectView(presDocView, false); - } + }; //Regular click @action @@ -766,12 +823,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { if (doc.presPinView || doc.presentationTargetDoc === this.layoutDoc.presCollection) setTimeout(() => this.updateCurrentPresentation(context), 0); else this.updateCurrentPresentation(context); - if (this.activeItem.setPosition && - this.activeItem.y !== undefined && - this.activeItem.x !== undefined && - this.targetDoc.x !== undefined && - this.targetDoc.y !== undefined) { - const timer = (ms: number) => new Promise(res => this._presTimer = setTimeout(res, ms)); + if (this.activeItem.setPosition && this.activeItem.y !== undefined && this.activeItem.x !== undefined && this.targetDoc.x !== undefined && this.targetDoc.y !== undefined) { + const timer = (ms: number) => new Promise(res => (this._presTimer = setTimeout(res, ms))); const time = 10; const ydiff = NumCast(this.activeItem.y) - NumCast(this.targetDoc.y); const xdiff = NumCast(this.activeItem.x) - NumCast(this.targetDoc.x); @@ -782,7 +835,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { await timer(0.1); } } - } + }; //Command click @action @@ -797,13 +850,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { this.removeFromArray(this._dragArray, doc); } this.selectPres(); - } + }; removeFromArray = (arr: any[], val: any) => { const index: number = arr.indexOf(val); const ret: any[] = arr.splice(index, 1); arr = ret; - } + }; //Shift click @action @@ -818,7 +871,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { } } this.selectPres(); - } + }; //regular click @action @@ -829,17 +882,17 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { this._dragArray.splice(0, this._dragArray.length, drag); focus && this.selectElement(doc); selectPres && this.selectPres(); - } + }; modifierSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement, focus: boolean, cmdClick: boolean, shiftClick: boolean) => { if (cmdClick) this.multiSelect(doc, ref, drag); else if (shiftClick) this.shiftSelect(doc, ref, drag); else this.regularSelect(doc, ref, drag, focus); - } + }; static keyEventsWrapper = (e: KeyboardEvent) => { PresBox.Instance.keyEvents(e); - } + }; // Key for when the presentaiton is active @action @@ -847,57 +900,75 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { if (e.target instanceof HTMLInputElement) return; let handled = false; const anchorNode = document.activeElement as HTMLDivElement; - if (anchorNode && anchorNode.className?.includes("lm_title")) return; + if (anchorNode && anchorNode.className?.includes('lm_title')) return; switch (e.key) { - case "Backspace": - if (this.layoutDoc.presStatus === "edit") { - undoBatch(action(() => { - for (const doc of Array.from(this._selectedArray.keys())) { - this.removeDocument(doc); - } - this._selectedArray.clear(); - this._eleArray.length = 0; - this._dragArray.length = 0; - }))(); + case 'Backspace': + if (this.layoutDoc.presStatus === 'edit') { + undoBatch( + action(() => { + for (const doc of Array.from(this._selectedArray.keys())) { + this.removeDocument(doc); + } + this._selectedArray.clear(); + this._eleArray.length = 0; + this._dragArray.length = 0; + }) + )(); handled = true; } break; - case "Escape": - if (DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.layoutDoc)) { this.updateMinimize(); } - else if (this.layoutDoc.presStatus === "edit") { this._selectedArray.clear(); this._eleArray.length = this._dragArray.length = 0; } - else this.layoutDoc.presStatus = "edit"; + case 'Escape': + if (DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.layoutDoc)) { + this.updateMinimize(); + } else if (this.layoutDoc.presStatus === 'edit') { + this._selectedArray.clear(); + this._eleArray.length = this._dragArray.length = 0; + } else this.layoutDoc.presStatus = 'edit'; if (this._presTimer) clearTimeout(this._presTimer); handled = true; break; - case "Down": case "ArrowDown": - case "Right": case "ArrowRight": - if (e.shiftKey && this.itemIndex < this.childDocs.length - 1) { // TODO: update to work properly + case 'Down': + case 'ArrowDown': + case 'Right': + case 'ArrowRight': + if (e.shiftKey && this.itemIndex < this.childDocs.length - 1) { + // TODO: update to work properly this.rootDoc._itemIndex = NumCast(this.rootDoc._itemIndex) + 1; this._selectedArray.set(this.childDocs[this.rootDoc._itemIndex], undefined); } else { this.next(); - if (this._presTimer) { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; } + if (this._presTimer) { + clearTimeout(this._presTimer); + this.layoutDoc.presStatus = PresStatus.Manual; + } } handled = true; break; - case "Up": case "ArrowUp": - case "Left": case "ArrowLeft": - if (e.shiftKey && this.itemIndex !== 0) { // TODO: update to work properly + case 'Up': + case 'ArrowUp': + case 'Left': + case 'ArrowLeft': + if (e.shiftKey && this.itemIndex !== 0) { + // TODO: update to work properly this.rootDoc._itemIndex = NumCast(this.rootDoc._itemIndex) - 1; this._selectedArray.set(this.childDocs[this.rootDoc._itemIndex], undefined); } else { this.back(); - if (this._presTimer) { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; } + if (this._presTimer) { + clearTimeout(this._presTimer); + this.layoutDoc.presStatus = PresStatus.Manual; + } } handled = true; break; - case "Spacebar": case " ": + case 'Spacebar': + case ' ': if (this.layoutDoc.presStatus === PresStatus.Manual) this.startAutoPres(this.itemIndex); else if (this.layoutDoc.presStatus === PresStatus.Autoplay) if (this._presTimer) clearTimeout(this._presTimer); handled = true; break; - case "a": - if ((e.metaKey || e.altKey) && this.layoutDoc.presStatus === "edit") { + case 'a': + if ((e.metaKey || e.altKey) && this.layoutDoc.presStatus === 'edit') { this._selectedArray.clear(); this.childDocs.forEach(doc => this._selectedArray.set(doc, undefined)); handled = true; @@ -909,10 +980,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { e.stopPropagation(); e.preventDefault(); } - } + }; /** - * + * */ @action viewPaths = () => { @@ -920,7 +991,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { if (srcContext) { this.togglePath(srcContext); } - } + }; getAllIndexes = (arr: Doc[], val: Doc): number[] => { const indexes = []; @@ -928,7 +999,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { arr[i] === val && indexes.push(i); } return indexes; - } + }; // Adds the index in the pres path graphically @computed get order() { @@ -936,61 +1007,64 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const docs: Doc[] = []; const presCollection = Cast(this.rootDoc.presCollection, Doc, null); const dv = DocumentManager.Instance.getDocumentView(presCollection); - this.childDocs.filter(doc => Cast(doc.presentationTargetDoc, Doc, null)).forEach((doc, index) => { - const tagDoc = Cast(doc.presentationTargetDoc, Doc, null); - const srcContext = Cast(tagDoc.context, Doc, null); - const width = NumCast(tagDoc._width) / 10; - const height = Math.max(NumCast(tagDoc._height) / 10, 15); - const edge = Math.max(width, height); - const fontSize = edge * 0.8; - const gap = 2; - if (presCollection === srcContext) { - // Case A: Document is contained within the collection - if (docs.includes(tagDoc)) { - const prevOccurances: number = this.getAllIndexes(docs, tagDoc).length; - docs.push(tagDoc); - order.push( - <div className="pathOrder" - key={tagDoc.id + 'pres' + index} - style={{ top: NumCast(tagDoc.y) + (prevOccurances * (edge + gap) - (edge / 2)), left: NumCast(tagDoc.x) - (edge / 2), width: edge, height: edge, fontSize: fontSize }} - onClick={() => this.selectElement(doc)}> - <div className="pathOrder-frame">{index + 1}</div> - </div>); - } else { + this.childDocs + .filter(doc => Cast(doc.presentationTargetDoc, Doc, null)) + .forEach((doc, index) => { + const tagDoc = Cast(doc.presentationTargetDoc, Doc, null); + const srcContext = Cast(tagDoc.context, Doc, null); + const width = NumCast(tagDoc._width) / 10; + const height = Math.max(NumCast(tagDoc._height) / 10, 15); + const edge = Math.max(width, height); + const fontSize = edge * 0.8; + const gap = 2; + if (presCollection === srcContext) { + // Case A: Document is contained within the collection + if (docs.includes(tagDoc)) { + const prevOccurances: number = this.getAllIndexes(docs, tagDoc).length; + docs.push(tagDoc); + order.push( + <div + className="pathOrder" + key={tagDoc.id + 'pres' + index} + style={{ top: NumCast(tagDoc.y) + (prevOccurances * (edge + gap) - edge / 2), left: NumCast(tagDoc.x) - edge / 2, width: edge, height: edge, fontSize: fontSize }} + onClick={() => this.selectElement(doc)}> + <div className="pathOrder-frame">{index + 1}</div> + </div> + ); + } else { + docs.push(tagDoc); + order.push( + <div + className="pathOrder" + key={tagDoc.id + 'pres' + index} + style={{ top: NumCast(tagDoc.y) - edge / 2, left: NumCast(tagDoc.x) - edge / 2, width: edge, height: edge, fontSize: fontSize }} + onClick={() => this.selectElement(doc)}> + <div className="pathOrder-frame">{index + 1}</div> + </div> + ); + } + } else if (doc.presPinView && presCollection === tagDoc && dv) { + // Case B: Document is presPinView and is presCollection + const scale: number = 1 / NumCast(doc.presPinViewScale); + const height: number = dv.props.PanelHeight() * scale; + const width: number = dv.props.PanelWidth() * scale; + const indWidth = width / 10; + const indHeight = Math.max(height / 10, 15); + const indEdge = Math.max(indWidth, indHeight); + const indFontSize = indEdge * 0.8; + const xLoc: number = NumCast(doc.presPinViewX) - width / 2; + const yLoc: number = NumCast(doc.presPinViewY) - height / 2; docs.push(tagDoc); order.push( - <div className="pathOrder" - key={tagDoc.id + 'pres' + index} - style={{ top: NumCast(tagDoc.y) - (edge / 2), left: NumCast(tagDoc.x) - (edge / 2), width: edge, height: edge, fontSize: fontSize }} - onClick={() => this.selectElement(doc)}> - <div className="pathOrder-frame">{index + 1}</div> - </div>); + <> + <div className="pathOrder" key={tagDoc.id + 'pres' + index} style={{ top: yLoc - indEdge / 2, left: xLoc - indEdge / 2, width: indEdge, height: indEdge, fontSize: indFontSize }} onClick={() => this.selectElement(doc)}> + <div className="pathOrder-frame">{index + 1}</div> + </div> + <div className="pathOrder-presPinView" style={{ top: yLoc, left: xLoc, width: width, height: height, borderWidth: indEdge / 10 }}></div> + </> + ); } - } else if (doc.presPinView && presCollection === tagDoc && dv) { - // Case B: Document is presPinView and is presCollection - const scale: number = 1 / NumCast(doc.presPinViewScale); - const height: number = dv.props.PanelHeight() * scale; - const width: number = dv.props.PanelWidth() * scale; - const indWidth = width / 10; - const indHeight = Math.max(height / 10, 15); - const indEdge = Math.max(indWidth, indHeight); - const indFontSize = indEdge * 0.8; - const xLoc: number = NumCast(doc.presPinViewX) - (width / 2); - const yLoc: number = NumCast(doc.presPinViewY) - (height / 2); - docs.push(tagDoc); - order.push( - <> - <div className="pathOrder" - key={tagDoc.id + 'pres' + index} - style={{ top: yLoc - (indEdge / 2), left: xLoc - (indEdge / 2), width: indEdge, height: indEdge, fontSize: indFontSize }} - onClick={() => this.selectElement(doc)} - > - <div className="pathOrder-frame">{index + 1}</div> - </div> - <div className="pathOrder-presPinView" style={{ top: yLoc, left: xLoc, width: width, height: height, borderWidth: indEdge / 10 }}></div> - </>); - } - }); + }); return order; } @@ -1003,37 +1077,39 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { * collection) */ @computed get paths() { - let pathPoints = ""; + let pathPoints = ''; const presCollection = Cast(this.rootDoc.presCollection, Doc, null); this.childDocs.forEach((doc, index) => { const tagDoc = Cast(doc.presentationTargetDoc, Doc, null); const srcContext = Cast(tagDoc?.context, Doc, null); if (tagDoc && presCollection === srcContext) { - const n1x = NumCast(tagDoc.x) + (NumCast(tagDoc._width) / 2); - const n1y = NumCast(tagDoc.y) + (NumCast(tagDoc._height) / 2); - if (index = 0) pathPoints = n1x + "," + n1y; - else pathPoints = pathPoints + " " + n1x + "," + n1y; + const n1x = NumCast(tagDoc.x) + NumCast(tagDoc._width) / 2; + const n1y = NumCast(tagDoc.y) + NumCast(tagDoc._height) / 2; + if ((index = 0)) pathPoints = n1x + ',' + n1y; + else pathPoints = pathPoints + ' ' + n1x + ',' + n1y; } else if (doc.presPinView && presCollection === tagDoc) { const n1x = NumCast(doc.presPinViewX); const n1y = NumCast(doc.presPinViewY); - if (index = 0) pathPoints = n1x + "," + n1y; - else pathPoints = pathPoints + " " + n1x + "," + n1y; + if ((index = 0)) pathPoints = n1x + ',' + n1y; + else pathPoints = pathPoints + ' ' + n1x + ',' + n1y; } }); - return (<polyline - points={pathPoints} - style={{ - opacity: 1, - stroke: "#69a6db", - strokeWidth: 5, - strokeDasharray: '10 5', - boxShadow: '0px 4px 4px rgba(0, 0, 0, 0.25)', - }} - fill="none" - markerStart="url(#markerArrow)" - markerMid="url(#markerSquare)" - markerEnd="url(#markerSquareFilled)" - />); + return ( + <polyline + points={pathPoints} + style={{ + opacity: 1, + stroke: '#69a6db', + strokeWidth: 5, + strokeDasharray: '10 5', + boxShadow: '0px 4px 4px rgba(0, 0, 0, 0.25)', + }} + fill="none" + markerStart="url(#markerArrow)" + markerMid="url(#markerSquare)" + markerEnd="url(#markerSquareFilled)" + /> + ); } // Converts seconds to ms and updates presTransition @@ -1042,8 +1118,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { if (change) timeInMS += change; if (timeInMS < 100) timeInMS = 100; if (timeInMS > 10000) timeInMS = 10000; - Array.from(this._selectedArray.keys()).forEach((doc) => doc.presTransition = timeInMS); - } + Array.from(this._selectedArray.keys()).forEach(doc => (doc.presTransition = timeInMS)); + }; // Converts seconds to ms and updates presTransition setZoom = (number: String, change?: number) => { @@ -1051,8 +1127,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { if (change) scale += change; if (scale < 0.01) scale = 0.01; if (scale > 1.5) scale = 1.5; - Array.from(this._selectedArray.keys()).forEach((doc) => doc.presZoom = scale); - } + Array.from(this._selectedArray.keys()).forEach(doc => (doc.presZoom = scale)); + }; // Converts seconds to ms and updates presDuration setDurationTime = (number: String, change?: number) => { @@ -1060,8 +1136,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { if (change) timeInMS += change; if (timeInMS < 100) timeInMS = 100; if (timeInMS > 20000) timeInMS = 20000; - Array.from(this._selectedArray.keys()).forEach((doc) => doc.presDuration = timeInMS); - } + Array.from(this._selectedArray.keys()).forEach(doc => (doc.presDuration = timeInMS)); + }; /** * When the movement dropdown is changes @@ -1069,7 +1145,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { @undoBatch updateMovement = action((movement: any, all?: boolean) => { const array: any[] = all ? this.childDocs : Array.from(this._selectedArray.keys()); - array.forEach((doc) => { + array.forEach(doc => { switch (movement) { case PresMovement.Zoom: //Pan and zoom doc.presMovement = PresMovement.Zoom; @@ -1081,7 +1157,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { doc.presJump = true; doc.presMovement = PresMovement.Jump; break; - case PresMovement.None: default: + case PresMovement.None: + default: doc.presMovement = PresMovement.None; break; } @@ -1092,31 +1169,31 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { @action updateHideBefore = (activeItem: Doc) => { activeItem.presHideBefore = !activeItem.presHideBefore; - Array.from(this._selectedArray.keys()).forEach((doc) => doc.presHideBefore = activeItem.presHideBefore); - } + Array.from(this._selectedArray.keys()).forEach(doc => (doc.presHideBefore = activeItem.presHideBefore)); + }; @undoBatch @action updateHideAfter = (activeItem: Doc) => { activeItem.presHideAfter = !activeItem.presHideAfter; - Array.from(this._selectedArray.keys()).forEach((doc) => doc.presHideAfter = activeItem.presHideAfter); - } + Array.from(this._selectedArray.keys()).forEach(doc => (doc.presHideAfter = activeItem.presHideAfter)); + }; @undoBatch @action updateOpenDoc = (activeItem: Doc) => { activeItem.openDocument = !activeItem.openDocument; - Array.from(this._selectedArray.keys()).forEach((doc) => { + Array.from(this._selectedArray.keys()).forEach(doc => { doc.openDocument = activeItem.openDocument; }); - } + }; @undoBatch @action updateEffectDirection = (effect: any, all?: boolean) => { const array: any[] = all ? this.childDocs : Array.from(this._selectedArray.keys()); - array.forEach((doc) => { - const tagDoc = doc;// Cast(doc.presentationTargetDoc, Doc, null); + array.forEach(doc => { + const tagDoc = doc; // Cast(doc.presentationTargetDoc, Doc, null); switch (effect) { case PresEffect.Left: tagDoc.presEffectDirection = PresEffect.Left; @@ -1130,19 +1207,20 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { case PresEffect.Bottom: tagDoc.presEffectDirection = PresEffect.Bottom; break; - case PresEffect.Center: default: + case PresEffect.Center: + default: tagDoc.presEffectDirection = PresEffect.Center; break; } }); - } + }; @undoBatch @action updateEffect = (effect: any, all?: boolean) => { const array: any[] = all ? this.childDocs : Array.from(this._selectedArray.keys()); - array.forEach((doc) => { - const tagDoc = doc;//Cast(doc.presentationTargetDoc, Doc, null); + array.forEach(doc => { + const tagDoc = doc; //Cast(doc.presentationTargetDoc, Doc, null); switch (effect) { case PresEffect.Bounce: tagDoc.presEffect = PresEffect.Bounce; @@ -1159,19 +1237,20 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { case PresEffect.Rotate: tagDoc.presEffect = PresEffect.Rotate; break; - case PresEffect.None: default: + case PresEffect.None: + default: tagDoc.presEffect = PresEffect.None; break; } }); - } + }; _batch: UndoManager.Batch | undefined = undefined; @computed get transitionDropdown() { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; - const isPresCollection: boolean = (targetDoc === this.layoutDoc.presCollection); + const isPresCollection: boolean = targetDoc === this.layoutDoc.presCollection; const isPinWithView: boolean = BoolCast(activeItem.presPinView); if (activeItem && targetDoc) { const type = targetDoc.type; @@ -1182,155 +1261,301 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const effect = this.activeItem.presEffect ? this.activeItem.presEffect : 'None'; activeItem.presMovement = activeItem.presMovement ? activeItem.presMovement : 'Zoom'; return ( - <div className={`presBox-ribbon ${this.transitionTools && this.layoutDoc.presStatus === PresStatus.Edit ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onClick={action(e => { e.stopPropagation(); this.openMovementDropdown = false; this.openEffectDropdown = false; })}> + <div + className={`presBox-ribbon ${this.transitionTools && this.layoutDoc.presStatus === PresStatus.Edit ? 'active' : ''}`} + onPointerDown={e => e.stopPropagation()} + onPointerUp={e => e.stopPropagation()} + onClick={action(e => { + e.stopPropagation(); + this.openMovementDropdown = false; + this.openEffectDropdown = false; + })}> <div className="ribbon-box"> Movement - {isPresCollection || (isPresCollection && isPinWithView) ? + {isPresCollection || (isPresCollection && isPinWithView) ? ( <div className="ribbon-property" style={{ marginLeft: 0, height: 25, textAlign: 'left', paddingLeft: 5, paddingRight: 5, fontSize: 10 }}> - {this.scrollable ? "Scroll to pinned view" : !isPinWithView ? "No movement" : "Pan & Zoom to pinned view"} + {this.scrollable ? 'Scroll to pinned view' : !isPinWithView ? 'No movement' : 'Pan & Zoom to pinned view'} </div> - : - <div className="presBox-dropdown" onClick={action(e => { e.stopPropagation(); this.openMovementDropdown = !this.openMovementDropdown; })} style={{ borderBottomLeftRadius: this.openMovementDropdown ? 0 : 5, border: this.openMovementDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}> + ) : ( + <div + className="presBox-dropdown" + onClick={action(e => { + e.stopPropagation(); + this.openMovementDropdown = !this.openMovementDropdown; + })} + style={{ borderBottomLeftRadius: this.openMovementDropdown ? 0 : 5, border: this.openMovementDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}> {this.setMovementName(activeItem.presMovement, activeItem)} - <FontAwesomeIcon className='presBox-dropdownIcon' style={{ gridColumn: 2, color: this.openMovementDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={"angle-down"} /> - <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} onPointerDown={e => e.stopPropagation()} style={{ display: this.openMovementDropdown ? "grid" : "none" }}> - <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.None ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.None)}>None</div> - <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.Zoom ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Zoom)}>Pan {"&"} Zoom</div> - <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.Pan ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Pan)}>Pan</div> - <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.Jump ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Jump)}>Jump cut</div> + <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this.openMovementDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} /> + <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} onPointerDown={e => e.stopPropagation()} style={{ display: this.openMovementDropdown ? 'grid' : 'none' }}> + <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.None ? 'active' : ''}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.None)}> + None + </div> + <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.Zoom ? 'active' : ''}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Zoom)}> + Pan {'&'} Zoom + </div> + <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.Pan ? 'active' : ''}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Pan)}> + Pan + </div> + <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.Jump ? 'active' : ''}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Jump)}> + Jump cut + </div> </div> </div> - } - <div className="ribbon-doubleButton" style={{ display: activeItem.presMovement === PresMovement.Zoom ? "inline-flex" : "none" }}> + )} + <div className="ribbon-doubleButton" style={{ display: activeItem.presMovement === PresMovement.Zoom ? 'inline-flex' : 'none' }}> <div className="presBox-subheading">Zoom (% screen filled)</div> <div className="ribbon-property"> - <input className="presBox-input" - type="number" value={zoom} - onChange={action((e) => this.setZoom(e.target.value))} />% + <input className="presBox-input" type="number" value={zoom} onChange={action(e => this.setZoom(e.target.value))} />% </div> <div className="ribbon-propertyUpDown"> <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setZoom(String(zoom), 0.1))}> - <FontAwesomeIcon icon={"caret-up"} /> + <FontAwesomeIcon icon={'caret-up'} /> </div> <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setZoom(String(zoom), -0.1))}> - <FontAwesomeIcon icon={"caret-down"} /> + <FontAwesomeIcon icon={'caret-down'} /> </div> </div> </div> - <input type="range" step="1" min="0" max="150" value={zoom} - className={`toolbar-slider ${activeItem.presMovement === PresMovement.Zoom ? "" : "none"}`} + <input + type="range" + step="1" + min="0" + max="150" + value={zoom} + className={`toolbar-slider ${activeItem.presMovement === PresMovement.Zoom ? '' : 'none'}`} id="toolbar-slider" - onPointerDown={() => this._batch = UndoManager.StartBatch("presZoom")} + onPointerDown={() => (this._batch = UndoManager.StartBatch('presZoom'))} onPointerUp={() => this._batch?.end()} onChange={(e: React.ChangeEvent<HTMLInputElement>) => { e.stopPropagation(); this.setZoom(e.target.value); - }} /> - <div className="ribbon-doubleButton" style={{ display: activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? "inline-flex" : "none" }}> + }} + /> + <div className="ribbon-doubleButton" style={{ display: activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? 'inline-flex' : 'none' }}> <div className="presBox-subheading">Movement Speed</div> <div className="ribbon-property"> - <input className="presBox-input" - type="number" value={transitionSpeed} - onKeyDown={e => e.stopPropagation()} - onChange={action((e) => this.setTransitionTime(e.target.value))} /> s + <input className="presBox-input" type="number" value={transitionSpeed} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.setTransitionTime(e.target.value))} /> s </div> <div className="ribbon-propertyUpDown"> <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setTransitionTime(String(transitionSpeed), 1000))}> - <FontAwesomeIcon icon={"caret-up"} /> + <FontAwesomeIcon icon={'caret-up'} /> </div> <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setTransitionTime(String(transitionSpeed), -1000))}> - <FontAwesomeIcon icon={"caret-down"} /> + <FontAwesomeIcon icon={'caret-down'} /> </div> </div> </div> - <input type="range" step="0.1" min="0.1" max="10" value={transitionSpeed} - className={`toolbar-slider ${activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? "" : "none"}`} + <input + type="range" + step="0.1" + min="0.1" + max="10" + value={transitionSpeed} + className={`toolbar-slider ${activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? '' : 'none'}`} id="toolbar-slider" - onPointerDown={() => this._batch = UndoManager.StartBatch("presTransition")} + onPointerDown={() => (this._batch = UndoManager.StartBatch('presTransition'))} onPointerUp={() => this._batch?.end()} onChange={(e: React.ChangeEvent<HTMLInputElement>) => { e.stopPropagation(); this.setTransitionTime(e.target.value); - }} /> - <div className={`slider-headers ${activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? "" : "none"}`}> + }} + /> + <div className={`slider-headers ${activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? '' : 'none'}`}> <div className="slider-text">Fast</div> <div className="slider-text">Medium</div> <div className="slider-text">Slow</div> </div> </div> <div className="ribbon-box"> - Visibility {"&"} Duration + Visibility {'&'} Duration <div className="ribbon-doubleButton"> - {isPresCollection ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Hide before presented"}</div></>}><div className={`ribbon-toggle ${activeItem.presHideBefore ? "active" : ""}`} onClick={() => this.updateHideBefore(activeItem)}>Hide before</div></Tooltip>} - {isPresCollection ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Hide after presented"}</div></>}><div className={`ribbon-toggle ${activeItem.presHideAfter ? "active" : ""}`} onClick={() => this.updateHideAfter(activeItem)}>Hide after</div></Tooltip>} - <Tooltip title={<><div className="dash-tooltip">{"Open in lightbox view"}</div></>}><div className="ribbon-toggle" style={{ backgroundColor: activeItem.openDocument ? Colors.LIGHT_BLUE : "" }} onClick={() => this.updateOpenDoc(activeItem)}>Lightbox</div></Tooltip> + {isPresCollection ? null : ( + <Tooltip + title={ + <> + <div className="dash-tooltip">{'Hide before presented'}</div> + </> + }> + <div className={`ribbon-toggle ${activeItem.presHideBefore ? 'active' : ''}`} onClick={() => this.updateHideBefore(activeItem)}> + Hide before + </div> + </Tooltip> + )} + {isPresCollection ? null : ( + <Tooltip + title={ + <> + <div className="dash-tooltip">{'Hide after presented'}</div> + </> + }> + <div className={`ribbon-toggle ${activeItem.presHideAfter ? 'active' : ''}`} onClick={() => this.updateHideAfter(activeItem)}> + Hide after + </div> + </Tooltip> + )} + <Tooltip + title={ + <> + <div className="dash-tooltip">{'Open in lightbox view'}</div> + </> + }> + <div className="ribbon-toggle" style={{ backgroundColor: activeItem.openDocument ? Colors.LIGHT_BLUE : '' }} onClick={() => this.updateOpenDoc(activeItem)}> + Lightbox + </div> + </Tooltip> </div> - {(type === DocumentType.AUDIO || type === DocumentType.VID) ? (null) : <> - <div className="ribbon-doubleButton" > - <div className="presBox-subheading">Slide Duration</div> - <div className="ribbon-property"> - <input className="presBox-input" - type="number" value={duration} - onKeyDown={e => e.stopPropagation()} - onChange={action((e) => this.setDurationTime(e.target.value))} /> s + {type === DocumentType.AUDIO || type === DocumentType.VID ? null : ( + <> + <div className="ribbon-doubleButton"> + <div className="presBox-subheading">Slide Duration</div> + <div className="ribbon-property"> + <input className="presBox-input" type="number" value={duration} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.setDurationTime(e.target.value))} /> s + </div> + <div className="ribbon-propertyUpDown"> + <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setDurationTime(String(duration), 1000))}> + <FontAwesomeIcon icon={'caret-up'} /> + </div> + <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setDurationTime(String(duration), -1000))}> + <FontAwesomeIcon icon={'caret-down'} /> + </div> + </div> + </div> + <input + type="range" + step="0.1" + min="0.1" + max="20" + value={duration} + style={{ display: targetDoc.type === DocumentType.AUDIO ? 'none' : 'block' }} + className={'toolbar-slider'} + id="duration-slider" + onPointerDown={() => { + this._batch = UndoManager.StartBatch('presDuration'); + }} + onPointerUp={() => { + if (this._batch) this._batch.end(); + }} + onChange={(e: React.ChangeEvent<HTMLInputElement>) => { + e.stopPropagation(); + this.setDurationTime(e.target.value); + }} + /> + <div className={'slider-headers'} style={{ display: targetDoc.type === DocumentType.AUDIO ? 'none' : 'grid' }}> + <div className="slider-text">Short</div> + <div className="slider-text">Medium</div> + <div className="slider-text">Long</div> </div> - <div className="ribbon-propertyUpDown"> - <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setDurationTime(String(duration), 1000))}> - <FontAwesomeIcon icon={"caret-up"} /> + </> + )} + </div> + {isPresCollection ? null : ( + <div className="ribbon-box"> + Effects + <div + className="presBox-dropdown" + onClick={action(e => { + e.stopPropagation(); + this.openEffectDropdown = !this.openEffectDropdown; + })} + style={{ borderBottomLeftRadius: this.openEffectDropdown ? 0 : 5, border: this.openEffectDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}> + {effect.toString()} + <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this.openEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} /> + <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} style={{ display: this.openEffectDropdown ? 'grid' : 'none' }} onPointerDown={e => e.stopPropagation()}> + <div + className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.None || !this.activeItem.presEffect ? 'active' : ''}`} + onPointerDown={e => e.stopPropagation()} + onClick={() => this.updateEffect(PresEffect.None)}> + None </div> - <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setDurationTime(String(duration), -1000))}> - <FontAwesomeIcon icon={"caret-down"} /> + <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.Fade ? 'active' : ''}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Fade)}> + Fade In + </div> + <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.Flip ? 'active' : ''}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Flip)}> + Flip + </div> + <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.Rotate ? 'active' : ''}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Rotate)}> + Rotate + </div> + <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.Bounce ? 'active' : ''}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Bounce)}> + Bounce + </div> + <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.Roll ? 'active' : ''}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Roll)}> + Roll </div> </div> </div> - <input type="range" step="0.1" min="0.1" max="20" value={duration} - style={{ display: targetDoc.type === DocumentType.AUDIO ? "none" : "block" }} - className={"toolbar-slider"} id="duration-slider" - onPointerDown={() => { this._batch = UndoManager.StartBatch("presDuration"); }} - onPointerUp={() => { if (this._batch) this._batch.end(); }} - onChange={(e: React.ChangeEvent<HTMLInputElement>) => { e.stopPropagation(); this.setDurationTime(e.target.value); }} - /> - <div className={"slider-headers"} style={{ display: targetDoc.type === DocumentType.AUDIO ? "none" : "grid" }}> - <div className="slider-text">Short</div> - <div className="slider-text">Medium</div> - <div className="slider-text">Long</div> + <div className="ribbon-doubleButton" style={{ display: effect === 'None' ? 'none' : 'inline-flex' }}> + <div className="presBox-subheading">Effect direction</div> + <div className="ribbon-property">{this.effectDirection}</div> </div> - </>} - </div> - {isPresCollection ? (null) : <div className="ribbon-box"> - Effects - <div className="presBox-dropdown" onClick={action(e => { e.stopPropagation(); this.openEffectDropdown = !this.openEffectDropdown; })} style={{ borderBottomLeftRadius: this.openEffectDropdown ? 0 : 5, border: this.openEffectDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}> - {effect} - <FontAwesomeIcon className='presBox-dropdownIcon' style={{ gridColumn: 2, color: this.openEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={"angle-down"} /> - <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} style={{ display: this.openEffectDropdown ? "grid" : "none" }} onPointerDown={e => e.stopPropagation()}> - <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.None || !this.activeItem.presEffect ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.None)}>None</div> - <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.Fade ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Fade)}>Fade In</div> - <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.Flip ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Flip)}>Flip</div> - <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.Rotate ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Rotate)}>Rotate</div> - <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.Bounce ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Bounce)}>Bounce</div> - <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.Roll ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Roll)}>Roll</div> - </div> - </div> - <div className="ribbon-doubleButton" style={{ display: effect === 'None' ? "none" : "inline-flex" }}> - <div className="presBox-subheading" >Effect direction</div> - <div className="ribbon-property"> - {this.effectDirection} + <div className="effectDirection" style={{ display: effect === 'None' ? 'none' : 'grid', width: 40 }}> + <Tooltip title={<div className="dash-tooltip">{'Enter from left'}</div>}> + <div + style={{ gridColumn: 1, gridRow: 2, justifySelf: 'center', color: this.activeItem.presEffectDirection === PresEffect.Left ? Colors.LIGHT_BLUE : 'black', cursor: 'pointer' }} + onClick={() => this.updateEffectDirection(PresEffect.Left)}> + <FontAwesomeIcon icon={'angle-right'} /> + </div> + </Tooltip> + <Tooltip title={<div className="dash-tooltip">{'Enter from right'}</div>}> + <div + style={{ gridColumn: 3, gridRow: 2, justifySelf: 'center', color: this.activeItem.presEffectDirection === PresEffect.Right ? Colors.LIGHT_BLUE : 'black', cursor: 'pointer' }} + onClick={() => this.updateEffectDirection(PresEffect.Right)}> + <FontAwesomeIcon icon={'angle-left'} /> + </div> + </Tooltip> + <Tooltip + title={ + <> + <div className="dash-tooltip">{'Enter from top'}</div> + </> + }> + <div + style={{ gridColumn: 2, gridRow: 1, justifySelf: 'center', color: this.activeItem.presEffectDirection === PresEffect.Top ? Colors.LIGHT_BLUE : 'black', cursor: 'pointer' }} + onClick={() => this.updateEffectDirection(PresEffect.Top)}> + <FontAwesomeIcon icon={'angle-down'} /> + </div> + </Tooltip> + <Tooltip + title={ + <> + <div className="dash-tooltip">{'Enter from bottom'}</div> + </> + }> + <div + style={{ gridColumn: 2, gridRow: 3, justifySelf: 'center', color: this.activeItem.presEffectDirection === PresEffect.Bottom ? Colors.LIGHT_BLUE : 'black', cursor: 'pointer' }} + onClick={() => this.updateEffectDirection(PresEffect.Bottom)}> + <FontAwesomeIcon icon={'angle-up'} /> + </div> + </Tooltip> + <Tooltip + title={ + <> + <div className="dash-tooltip">{'Enter from center'}</div> + </> + }> + <div + style={{ + gridColumn: 2, + gridRow: 2, + width: 10, + height: 10, + alignSelf: 'center', + justifySelf: 'center', + border: this.activeItem.presEffectDirection === PresEffect.Center || !this.activeItem.presEffectDirection ? `solid 2px ${Colors.LIGHT_BLUE}` : 'solid 2px black', + borderRadius: '100%', + cursor: 'pointer', + }} + onClick={() => this.updateEffectDirection(PresEffect.Center)}></div> + </Tooltip> </div> </div> - <div className="effectDirection" style={{ display: effect === 'None' ? "none" : "grid", width: 40 }}> - <Tooltip title={<><div className="dash-tooltip">{"Enter from left"}</div></>}><div style={{ gridColumn: 1, gridRow: 2, justifySelf: 'center', color: this.activeItem.presEffectDirection === PresEffect.Left ? Colors.LIGHT_BLUE : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Left)}><FontAwesomeIcon icon={"angle-right"} /></div></Tooltip> - <Tooltip title={<><div className="dash-tooltip">{"Enter from right"}</div></>}><div style={{ gridColumn: 3, gridRow: 2, justifySelf: 'center', color: this.activeItem.presEffectDirection === PresEffect.Right ? Colors.LIGHT_BLUE : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Right)}><FontAwesomeIcon icon={"angle-left"} /></div></Tooltip> - <Tooltip title={<><div className="dash-tooltip">{"Enter from top"}</div></>}><div style={{ gridColumn: 2, gridRow: 1, justifySelf: 'center', color: this.activeItem.presEffectDirection === PresEffect.Top ? Colors.LIGHT_BLUE : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Top)}><FontAwesomeIcon icon={"angle-down"} /></div></Tooltip> - <Tooltip title={<><div className="dash-tooltip">{"Enter from bottom"}</div></>}><div style={{ gridColumn: 2, gridRow: 3, justifySelf: 'center', color: this.activeItem.presEffectDirection === PresEffect.Bottom ? Colors.LIGHT_BLUE : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Bottom)}><FontAwesomeIcon icon={"angle-up"} /></div></Tooltip> - <Tooltip title={<><div className="dash-tooltip">{"Enter from center"}</div></>}><div style={{ gridColumn: 2, gridRow: 2, width: 10, height: 10, alignSelf: 'center', justifySelf: 'center', border: this.activeItem.presEffectDirection === PresEffect.Center || !this.activeItem.presEffectDirection ? `solid 2px ${Colors.LIGHT_BLUE}` : "solid 2px black", borderRadius: "100%", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Center)}></div></Tooltip> - </div> - </div>} + )} <div className="ribbon-final-box"> <div className="ribbon-final-button-hidden" onClick={() => this.applyTo(this.childDocs)}> Apply to all </div> </div> - </div > + </div> ); } } @@ -1338,11 +1563,21 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { @computed get effectDirection(): string { let effect = ''; switch (this.activeItem.presEffectDirection) { - case 'left': effect = "Enter from left"; break; - case 'right': effect = "Enter from right"; break; - case 'top': effect = "Enter from top"; break; - case 'bottom': effect = "Enter from bottom"; break; - default: effect = "Enter from center"; break; + case 'left': + effect = 'Enter from left'; + break; + case 'right': + effect = 'Enter from right'; + break; + case 'top': + effect = 'Enter from top'; + break; + case 'bottom': + effect = 'Enter from bottom'; + break; + default: + effect = 'Enter from center'; + break; } return effect; } @@ -1355,7 +1590,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { this.updateMovement(activeItem.presMovement, true); this.updateEffect(activeItem.presEffect, true); this.updateEffectDirection(activeItem.presEffectDirection, true); - array.forEach((doc) => { + array.forEach(doc => { const curDoc = Cast(doc, Doc, null); const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null); if (tagDoc && targetDoc) { @@ -1365,63 +1600,86 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { curDoc.presHideAfter = activeItem.presHideAfter; } }); - } + }; @computed get presPinViewOptionsDropdown() { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; - const presPinWithViewIcon = <img src="/assets/pinWithView.png" style={{ margin: "auto", width: 16, filter: 'invert(1)' }} />; + const presPinWithViewIcon = <img src="/assets/pinWithView.png" style={{ margin: 'auto', width: 16, filter: 'invert(1)' }} />; return ( <> - {this.panable || this.scrollable || this.targetDoc.type === DocumentType.COMPARISON ? 'Pinned view' : (null)} + {this.panable || this.scrollable || this.targetDoc.type === DocumentType.COMPARISON ? 'Pinned view' : null} <div className="ribbon-doubleButton"> - <Tooltip title={<><div className="dash-tooltip">{activeItem.presPinView ? "Turn off pin with view" : "Turn on pin with view"}</div></>}><div className="ribbon-toggle" style={{ width: 20, padding: 0, backgroundColor: activeItem.presPinView ? Colors.LIGHT_BLUE : "" }} - onClick={() => { - activeItem.presPinView = !activeItem.presPinView; - targetDoc.presPinView = activeItem.presPinView; - if (activeItem.presPinView) { - if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.RTF || targetDoc.type === DocumentType.WEB || targetDoc._viewType === CollectionViewType.Stacking) { - const scroll = targetDoc._scrollTop; - activeItem.presPinView = true; - activeItem.presPinViewScroll = scroll; - } else if ([DocumentType.AUDIO, DocumentType.VID].includes(targetDoc.type as any)) { - activeItem.presStartTime = targetDoc._currentTimecode; - activeItem.presEndTime = NumCast(targetDoc._currentTimecode) + 0.1; - } else if ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG) { - const x = targetDoc._panX; - const y = targetDoc._panY; - const scale = targetDoc._viewScale; - activeItem.presPinView = true; - activeItem.presPinViewX = x; - activeItem.presPinViewY = y; - activeItem.presPinViewScale = scale; - } else if (targetDoc.type === DocumentType.COMPARISON) { - const width = targetDoc._clipWidth; - activeItem.presPinClipWidth = width; - activeItem.presPinView = true; + <Tooltip + title={ + <> + <div className="dash-tooltip">{activeItem.presPinView ? 'Turn off pin with view' : 'Turn on pin with view'}</div> + </> + }> + <div + className="ribbon-toggle" + style={{ width: 20, padding: 0, backgroundColor: activeItem.presPinView ? Colors.LIGHT_BLUE : '' }} + onClick={() => { + activeItem.presPinView = !activeItem.presPinView; + targetDoc.presPinView = activeItem.presPinView; + if (activeItem.presPinView) { + if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.RTF || targetDoc.type === DocumentType.WEB || targetDoc._viewType === CollectionViewType.Stacking) { + const scroll = targetDoc._scrollTop; + activeItem.presPinView = true; + activeItem.presPinViewScroll = scroll; + } else if ([DocumentType.AUDIO, DocumentType.VID].includes(targetDoc.type as any)) { + activeItem.presStartTime = targetDoc._currentTimecode; + activeItem.presEndTime = NumCast(targetDoc._currentTimecode) + 0.1; + } else if ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG) { + const x = targetDoc._panX; + const y = targetDoc._panY; + const scale = targetDoc._viewScale; + activeItem.presPinView = true; + activeItem.presPinViewX = x; + activeItem.presPinViewY = y; + activeItem.presPinViewScale = scale; + } else if (targetDoc.type === DocumentType.COMPARISON) { + const width = targetDoc._clipWidth; + activeItem.presPinClipWidth = width; + activeItem.presPinView = true; + } } - } - }}>{presPinWithViewIcon}</div></Tooltip> - {activeItem.presPinView ? <Tooltip title={<><div className="dash-tooltip">{"Update the pinned view with the view of the selected document"}</div></>}><div className="ribbon-button" - onClick={() => { - if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF) { - const scroll = targetDoc._scrollTop; - activeItem.presPinViewScroll = scroll; - } else if ([DocumentType.AUDIO, DocumentType.VID].includes(targetDoc.type as any)) { - activeItem.presStartTime = targetDoc._currentTimecode; - activeItem.presStartTime = NumCast(targetDoc._currentTimecode) + 0.1; - } else if (targetDoc.type === DocumentType.COMPARISON) { - const clipWidth = targetDoc._clipWidth; - activeItem.presPinClipWidth = clipWidth; - } else { - const x = targetDoc._panX; - const y = targetDoc._panY; - const scale = targetDoc._viewScale; - activeItem.presPinViewX = x; - activeItem.presPinViewY = y; - activeItem.presPinViewScale = scale; - } - }}>Update</div></Tooltip> : (null)} + }}> + {presPinWithViewIcon} + </div> + </Tooltip> + {activeItem.presPinView ? ( + <Tooltip + title={ + <> + <div className="dash-tooltip">{'Update the pinned view with the view of the selected document'}</div> + </> + }> + <div + className="ribbon-button" + onClick={() => { + if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF) { + const scroll = targetDoc._scrollTop; + activeItem.presPinViewScroll = scroll; + } else if ([DocumentType.AUDIO, DocumentType.VID].includes(targetDoc.type as any)) { + activeItem.presStartTime = targetDoc._currentTimecode; + activeItem.presStartTime = NumCast(targetDoc._currentTimecode) + 0.1; + } else if (targetDoc.type === DocumentType.COMPARISON) { + const clipWidth = targetDoc._clipWidth; + activeItem.presPinClipWidth = clipWidth; + } else { + const x = targetDoc._panX; + const y = targetDoc._panY; + const scale = targetDoc._viewScale; + activeItem.presPinViewX = x; + activeItem.presPinViewY = y; + activeItem.presPinViewScale = scale; + } + }}> + Update + </div> + </Tooltip> + ) : null} </div> </> ); @@ -1432,38 +1690,58 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const targetDoc: Doc = this.targetDoc; return ( <> - {this.panable ? <div style={{ display: activeItem.presPinView ? "block" : "none" }}> - <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> - <div className="presBox-subheading">Pan X</div> - <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> - <input className="presBox-input" - style={{ textAlign: 'left', width: 50 }} - type="number" value={NumCast(activeItem.presPinViewX)} - onKeyDown={e => e.stopPropagation()} - onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewX = Number(val); })} /> + {this.panable ? ( + <div style={{ display: activeItem.presPinView ? 'block' : 'none' }}> + <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> + <div className="presBox-subheading">Pan X</div> + <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> + <input + className="presBox-input" + style={{ textAlign: 'left', width: 50 }} + type="number" + value={NumCast(activeItem.presPinViewX)} + onKeyDown={e => e.stopPropagation()} + onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { + const val = e.target.value; + activeItem.presPinViewX = Number(val); + })} + /> + </div> </div> - </div> - <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> - <div className="presBox-subheading">Pan Y</div> - <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> - <input className="presBox-input" - style={{ textAlign: 'left', width: 50 }} - type="number" value={NumCast(activeItem.presPinViewY)} - onKeyDown={e => e.stopPropagation()} - onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewY = Number(val); })} /> + <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> + <div className="presBox-subheading">Pan Y</div> + <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> + <input + className="presBox-input" + style={{ textAlign: 'left', width: 50 }} + type="number" + value={NumCast(activeItem.presPinViewY)} + onKeyDown={e => e.stopPropagation()} + onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { + const val = e.target.value; + activeItem.presPinViewY = Number(val); + })} + /> + </div> </div> - </div> - <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> - <div className="presBox-subheading">Scale</div> - <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> - <input className="presBox-input" - style={{ textAlign: 'left', width: 50 }} - type="number" value={NumCast(activeItem.presPinViewScale)} - onKeyDown={e => e.stopPropagation()} - onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewScale = Number(val); })} /> + <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> + <div className="presBox-subheading">Scale</div> + <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> + <input + className="presBox-input" + style={{ textAlign: 'left', width: 50 }} + type="number" + value={NumCast(activeItem.presPinViewScale)} + onKeyDown={e => e.stopPropagation()} + onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { + const val = e.target.value; + activeItem.presPinViewScale = Number(val); + })} + /> + </div> </div> </div> - </div> : (null)} + ) : null} </> ); } @@ -1473,18 +1751,26 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const targetDoc: Doc = this.targetDoc; return ( <> - {this.scrollable ? <div style={{ display: activeItem.presPinView ? "block" : "none" }}> - <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> - <div className="presBox-subheading">Scroll</div> - <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> - <input className="presBox-input" - style={{ textAlign: 'left', width: 50 }} - type="number" value={NumCast(activeItem.presPinViewScroll)} - onKeyDown={e => e.stopPropagation()} - onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewScroll = Number(val); })} /> + {this.scrollable ? ( + <div style={{ display: activeItem.presPinView ? 'block' : 'none' }}> + <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> + <div className="presBox-subheading">Scroll</div> + <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> + <input + className="presBox-input" + style={{ textAlign: 'left', width: 50 }} + type="number" + value={NumCast(activeItem.presPinViewScroll)} + onKeyDown={e => e.stopPropagation()} + onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { + const val = e.target.value; + activeItem.presPinViewScroll = Number(val); + })} + /> + </div> </div> </div> - </div> : (null)} + ) : null} </> ); } @@ -1494,13 +1780,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const list = this.childDocs.map((doc, i) => { if (i > this.itemIndex) { return ( - <option>{i + 1}. {doc.title}</option> + <option> + {i + 1}. {StrCast(doc.title)} + </option> ); } }); - return ( - list - ); + return list; } @computed get mediaOptionsDropdown() { @@ -1510,25 +1796,29 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const clipEnd: number = NumCast(activeItem.clipEnd); const duration = Math.round(NumCast(activeItem[`${Doc.LayoutFieldKey(activeItem)}-duration`]) * 10); const mediaStopDocInd: number = NumCast(activeItem.mediaStopDoc); - const mediaStopDocStr: string = mediaStopDocInd ? mediaStopDocInd + ". " + this.childDocs[mediaStopDocInd - 1].title : ""; + const mediaStopDocStr: string = mediaStopDocInd ? mediaStopDocInd + '. ' + this.childDocs[mediaStopDocInd - 1].title : ''; if (activeItem && targetDoc) { return ( <div> <div className={'presBox-ribbon'} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> <div> <div className="ribbon-box"> - Start {"&"} End Time - <div className={"slider-headers"}> - <div className="slider-block" > + Start {'&'} End Time + <div className={'slider-headers'}> + <div className="slider-block"> <div className="slider-text" style={{ fontWeight: 500 }}> Start time (s) </div> - <div id={"startTime"} className="slider-number" style={{ backgroundColor: Colors.LIGHT_GRAY }}> - <input className="presBox-input" + <div id={'startTime'} className="slider-number" style={{ backgroundColor: Colors.LIGHT_GRAY }}> + <input + className="presBox-input" style={{ textAlign: 'center', width: 30, height: 15, fontSize: 10 }} - type="number" value={NumCast(activeItem.presStartTime)} + type="number" + value={NumCast(activeItem.presStartTime)} onKeyDown={e => e.stopPropagation()} - onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { activeItem.presStartTime = Number(e.target.value); })} + onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { + activeItem.presStartTime = Number(e.target.value); + })} /> </div> </div> @@ -1544,24 +1834,33 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <div className="slider-text" style={{ fontWeight: 500 }}> End time (s) </div> - <div id={"endTime"} className="slider-number" style={{ backgroundColor: Colors.LIGHT_GRAY }}> - <input className="presBox-input" + <div id={'endTime'} className="slider-number" style={{ backgroundColor: Colors.LIGHT_GRAY }}> + <input + className="presBox-input" onKeyDown={e => e.stopPropagation()} style={{ textAlign: 'center', width: 30, height: 15, fontSize: 10 }} - type="number" value={NumCast(activeItem.presEndTime)} - onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { activeItem.presEndTime = Number(e.target.value); })} + type="number" + value={NumCast(activeItem.presEndTime)} + onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { + activeItem.presEndTime = Number(e.target.value); + })} /> </div> </div> </div> <div className="multiThumb-slider"> - <input type="range" step="0.1" min={clipStart} max={clipEnd} value={NumCast(activeItem.presEndTime)} + <input + type="range" + step="0.1" + min={clipStart} + max={clipEnd} + value={NumCast(activeItem.presEndTime)} style={{ gridColumn: 1, gridRow: 1 }} - className={`toolbar-slider ${"end"}`} + className={`toolbar-slider ${'end'}`} id="toolbar-slider" onPointerDown={() => { - this._batch = UndoManager.StartBatch("presEndTime"); - const endBlock = document.getElementById("endTime"); + this._batch = UndoManager.StartBatch('presEndTime'); + const endBlock = document.getElementById('endTime'); if (endBlock) { endBlock.style.color = Colors.LIGHT_GRAY; endBlock.style.backgroundColor = Colors.MEDIUM_BLUE; @@ -1569,7 +1868,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { }} onPointerUp={() => { this._batch?.end(); - const endBlock = document.getElementById("endTime"); + const endBlock = document.getElementById('endTime'); if (endBlock) { endBlock.style.color = Colors.BLACK; endBlock.style.backgroundColor = Colors.LIGHT_GRAY; @@ -1578,14 +1877,20 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { onChange={(e: React.ChangeEvent<HTMLInputElement>) => { e.stopPropagation(); activeItem.presEndTime = Number(e.target.value); - }} /> - <input type="range" step="0.1" min={clipStart} max={clipEnd} value={NumCast(activeItem.presStartTime)} + }} + /> + <input + type="range" + step="0.1" + min={clipStart} + max={clipEnd} + value={NumCast(activeItem.presStartTime)} style={{ gridColumn: 1, gridRow: 1 }} - className={`toolbar-slider ${"start"}`} + className={`toolbar-slider ${'start'}`} id="toolbar-slider" onPointerDown={() => { - this._batch = UndoManager.StartBatch("presStartTime"); - const startBlock = document.getElementById("startTime"); + this._batch = UndoManager.StartBatch('presStartTime'); + const startBlock = document.getElementById('startTime'); if (startBlock) { startBlock.style.color = Colors.LIGHT_GRAY; startBlock.style.backgroundColor = Colors.MEDIUM_BLUE; @@ -1593,7 +1898,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { }} onPointerUp={() => { this._batch?.end(); - const startBlock = document.getElementById("startTime"); + const startBlock = document.getElementById('startTime'); if (startBlock) { startBlock.style.color = Colors.BLACK; startBlock.style.backgroundColor = Colors.LIGHT_GRAY; @@ -1602,9 +1907,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { onChange={(e: React.ChangeEvent<HTMLInputElement>) => { e.stopPropagation(); activeItem.presStartTime = Number(e.target.value); - }} /> + }} + /> </div> - <div className={`slider-headers ${activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? "" : "none"}`}> + <div className={`slider-headers ${activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? '' : 'none'}`}> <div className="slider-text">{clipStart} s</div> <div className="slider-text"></div> <div className="slider-text">{clipEnd} s</div> @@ -1615,38 +1921,22 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <div className="presBox-subheading">Start playing:</div> <div className="presBox-radioButtons"> <div className="checkbox-container"> - <input className="presBox-checkbox" - type="checkbox" - onChange={() => activeItem.mediaStart = "manual"} - checked={activeItem.mediaStart === "manual"} - /> + <input className="presBox-checkbox" type="checkbox" onChange={() => (activeItem.mediaStart = 'manual')} checked={activeItem.mediaStart === 'manual'} /> <div>On click</div> </div> <div className="checkbox-container"> - <input className="presBox-checkbox" - type="checkbox" - onChange={() => activeItem.mediaStart = "auto"} - checked={activeItem.mediaStart === "auto"} - /> + <input className="presBox-checkbox" type="checkbox" onChange={() => (activeItem.mediaStart = 'auto')} checked={activeItem.mediaStart === 'auto'} /> <div>Automatically</div> </div> </div> <div className="presBox-subheading">Stop playing:</div> <div className="presBox-radioButtons"> <div className="checkbox-container"> - <input className="presBox-checkbox" - type="checkbox" - onChange={() => activeItem.mediaStop = "manual"} - checked={activeItem.mediaStop === "manual"} - /> + <input className="presBox-checkbox" type="checkbox" onChange={() => (activeItem.mediaStop = 'manual')} checked={activeItem.mediaStop === 'manual'} /> <div>At audio end time</div> </div> <div className="checkbox-container"> - <input className="presBox-checkbox" - type="checkbox" - onChange={() => activeItem.mediaStop = "auto"} - checked={activeItem.mediaStop === "auto"} - /> + <input className="presBox-checkbox" type="checkbox" onChange={() => (activeItem.mediaStop = 'auto')} checked={activeItem.mediaStop === 'auto'} /> <div>On slide change</div> </div> {/* <div className="checkbox-container"> @@ -1670,7 +1960,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { </div> </div> </div> - </div > + </div> ); } } @@ -1678,84 +1968,137 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { @computed get newDocumentToolbarDropdown() { return ( <div> - <div className={'presBox-toolbar-dropdown'} style={{ display: this.newDocumentTools && this.layoutDoc.presStatus === "edit" ? "inline-flex" : "none" }} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> + <div + className={'presBox-toolbar-dropdown'} + style={{ display: this.newDocumentTools && this.layoutDoc.presStatus === 'edit' ? 'inline-flex' : 'none' }} + onClick={e => e.stopPropagation()} + onPointerUp={e => e.stopPropagation()} + onPointerDown={e => e.stopPropagation()}> <div className="layout-container" style={{ height: 'max-content' }}> - <div className="layout" style={{ border: this.layout === 'blank' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => { this.layout = 'blank'; this.createNewSlide(this.layout); })} /> - <div className="layout" style={{ border: this.layout === 'title' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => { this.layout = 'title'; this.createNewSlide(this.layout); })}> + <div + className="layout" + style={{ border: this.layout === 'blank' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} + onClick={action(() => { + this.layout = 'blank'; + this.createNewSlide(this.layout); + })} + /> + <div + className="layout" + style={{ border: this.layout === 'title' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} + onClick={action(() => { + this.layout = 'title'; + this.createNewSlide(this.layout); + })}> <div className="title">Title</div> <div className="subtitle">Subtitle</div> </div> - <div className="layout" style={{ border: this.layout === 'header' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => { this.layout = 'header'; this.createNewSlide(this.layout); })}> - <div className="title" style={{ alignSelf: 'center', fontSize: 10 }}>Section header</div> + <div + className="layout" + style={{ border: this.layout === 'header' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} + onClick={action(() => { + this.layout = 'header'; + this.createNewSlide(this.layout); + })}> + <div className="title" style={{ alignSelf: 'center', fontSize: 10 }}> + Section header + </div> </div> - <div className="layout" style={{ border: this.layout === 'content' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => { this.layout = 'content'; this.createNewSlide(this.layout); })}> - <div className="title" style={{ alignSelf: 'center' }}>Title</div> + <div + className="layout" + style={{ border: this.layout === 'content' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} + onClick={action(() => { + this.layout = 'content'; + this.createNewSlide(this.layout); + })}> + <div className="title" style={{ alignSelf: 'center' }}> + Title + </div> <div className="content">Text goes here</div> </div> </div> </div> - </div > + </div> ); } @observable openLayouts: boolean = false; @observable addFreeform: boolean = true; - @observable layout: string = ""; - @observable title: string = ""; + @observable layout: string = ''; + @observable title: string = ''; @computed get newDocumentDropdown() { return ( <div> - <div className={"presBox-ribbon"} onClick={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> + <div className={'presBox-ribbon'} onClick={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> <div className="ribbon-box"> Slide Title: <br></br> - <input className="ribbon-textInput" placeholder="..." type="text" name="fname" - onChange={(e) => { + <input + className="ribbon-textInput" + placeholder="..." + type="text" + name="fname" + onChange={e => { e.stopPropagation(); e.preventDefault(); - runInAction(() => this.title = e.target.value); - }}> - </input> + runInAction(() => (this.title = e.target.value)); + }}></input> </div> <div className="ribbon-box"> Choose type: <div className="ribbon-doubleButton"> - <div title="Text" className={'ribbon-toggle'} style={{ background: this.addFreeform ? "" : Colors.LIGHT_BLUE }} onClick={action(() => this.addFreeform = !this.addFreeform)}>Text</div> - <div title="Freeform" className={'ribbon-toggle'} style={{ background: this.addFreeform ? Colors.LIGHT_BLUE : "" }} onClick={action(() => this.addFreeform = !this.addFreeform)}>Freeform</div> + <div title="Text" className={'ribbon-toggle'} style={{ background: this.addFreeform ? '' : Colors.LIGHT_BLUE }} onClick={action(() => (this.addFreeform = !this.addFreeform))}> + Text + </div> + <div title="Freeform" className={'ribbon-toggle'} style={{ background: this.addFreeform ? Colors.LIGHT_BLUE : '' }} onClick={action(() => (this.addFreeform = !this.addFreeform))}> + Freeform + </div> </div> </div> - <div className="ribbon-box" style={{ display: this.addFreeform ? "grid" : "none" }}> + <div className="ribbon-box" style={{ display: this.addFreeform ? 'grid' : 'none' }}> Preset layouts: <div className="layout-container" style={{ height: this.openLayouts ? 'max-content' : '75px' }}> - <div className="layout" style={{ border: this.layout === 'blank' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => this.layout = 'blank')} /> - <div className="layout" style={{ border: this.layout === 'title' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => this.layout = 'title')}> + <div className="layout" style={{ border: this.layout === 'blank' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'blank'))} /> + <div className="layout" style={{ border: this.layout === 'title' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'title'))}> <div className="title">Title</div> <div className="subtitle">Subtitle</div> </div> - <div className="layout" style={{ border: this.layout === 'header' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => this.layout = 'header')}> - <div className="title" style={{ alignSelf: 'center', fontSize: 10 }}>Section header</div> + <div className="layout" style={{ border: this.layout === 'header' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'header'))}> + <div className="title" style={{ alignSelf: 'center', fontSize: 10 }}> + Section header + </div> </div> - <div className="layout" style={{ border: this.layout === 'content' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => this.layout = 'content')}> - <div className="title" style={{ alignSelf: 'center' }}>Title</div> + <div className="layout" style={{ border: this.layout === 'content' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'content'))}> + <div className="title" style={{ alignSelf: 'center' }}> + Title + </div> <div className="content">Text goes here</div> </div> - <div className="layout" style={{ border: this.layout === 'twoColumns' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => this.layout = 'twoColumns')}> - <div className="title" style={{ alignSelf: 'center', gridColumn: '1/3' }}>Title</div> - <div className="content" style={{ gridColumn: 1, gridRow: 2 }}>Column one text</div> - <div className="content" style={{ gridColumn: 2, gridRow: 2 }}>Column two text</div> + <div className="layout" style={{ border: this.layout === 'twoColumns' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'twoColumns'))}> + <div className="title" style={{ alignSelf: 'center', gridColumn: '1/3' }}> + Title + </div> + <div className="content" style={{ gridColumn: 1, gridRow: 2 }}> + Column one text + </div> + <div className="content" style={{ gridColumn: 2, gridRow: 2 }}> + Column two text + </div> </div> </div> - <div className="open-layout" onClick={action(() => this.openLayouts = !this.openLayouts)}> - <FontAwesomeIcon style={{ transition: 'all 0.3s', transform: this.openLayouts ? 'rotate(180deg)' : 'rotate(0deg)' }} icon={"caret-down"} size={"lg"} /> + <div className="open-layout" onClick={action(() => (this.openLayouts = !this.openLayouts))}> + <FontAwesomeIcon style={{ transition: 'all 0.3s', transform: this.openLayouts ? 'rotate(180deg)' : 'rotate(0deg)' }} icon={'caret-down'} size={'lg'} /> </div> </div> <div className="ribbon-final-box"> - <div className={this.title !== "" && (this.addFreeform && this.layout !== "" || !this.addFreeform) ? "ribbon-final-button-hidden" : "ribbon-final-button"} onClick={() => this.createNewSlide(this.layout, this.title, this.addFreeform)}> + <div + className={this.title !== '' && ((this.addFreeform && this.layout !== '') || !this.addFreeform) ? 'ribbon-final-button-hidden' : 'ribbon-final-button'} + onClick={() => this.createNewSlide(this.layout, this.title, this.addFreeform)}> Create New Slide </div> </div> </div> - </div > + </div> ); } @@ -1763,7 +2106,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { let doc = undefined; if (layout) doc = this.createTemplate(layout); if (freeform && layout) doc = this.createTemplate(layout, title); - if (!freeform && !layout) doc = Docs.Create.TextDocument("", { _nativeWidth: 400, _width: 225, title: title }); + if (!freeform && !layout) doc = Docs.Create.TextDocument('', { _nativeWidth: 400, _width: 225, title: title }); if (doc) { const presCollection = Cast(this.layoutDoc.presCollection, Doc, null); const data = Cast(presCollection?.data, listSpec(Doc)); @@ -1773,10 +2116,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { TabDocView.PinDoc(doc); this.gotoDocument(this.childDocs.length, this.activeItem); } else { - this.props.addDocTab(doc, "add:right"); + this.props.addDocTab(doc, 'add:right'); } } - } + }; createTemplate = (layout: string, input?: string) => { const activeItem: Doc = this.activeItem; @@ -1788,43 +2131,59 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { y = NumCast(targetDoc.y) + NumCast(targetDoc._height) + 20; } let doc = undefined; - const title = Docs.Create.TextDocument("Click to change title", { title: "Slide title", _width: 380, _height: 60, x: 10, y: 58, _fontSize: "24pt", }); - const subtitle = Docs.Create.TextDocument("Click to change subtitle", { title: "Slide subtitle", _width: 380, _height: 50, x: 10, y: 118, _fontSize: "16pt" }); - const header = Docs.Create.TextDocument("Click to change header", { title: "Slide header", _width: 380, _height: 65, x: 10, y: 80, _fontSize: "20pt" }); - const contentTitle = Docs.Create.TextDocument("Click to change title", { title: "Slide title", _width: 380, _height: 60, x: 10, y: 10, _fontSize: "24pt" }); - const content = Docs.Create.TextDocument("Click to change text", { title: "Slide text", _width: 380, _height: 145, x: 10, y: 70, _fontSize: "14pt" }); - const content1 = Docs.Create.TextDocument("Click to change text", { title: "Column 1", _width: 185, _height: 140, x: 10, y: 80, _fontSize: "14pt" }); - const content2 = Docs.Create.TextDocument("Click to change text", { title: "Column 2", _width: 185, _height: 140, x: 205, y: 80, _fontSize: "14pt" }); + const title = Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 58, _fontSize: '24pt' }); + const subtitle = Docs.Create.TextDocument('Click to change subtitle', { title: 'Slide subtitle', _width: 380, _height: 50, x: 10, y: 118, _fontSize: '16pt' }); + const header = Docs.Create.TextDocument('Click to change header', { title: 'Slide header', _width: 380, _height: 65, x: 10, y: 80, _fontSize: '20pt' }); + const contentTitle = Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 10, _fontSize: '24pt' }); + const content = Docs.Create.TextDocument('Click to change text', { title: 'Slide text', _width: 380, _height: 145, x: 10, y: 70, _fontSize: '14pt' }); + const content1 = Docs.Create.TextDocument('Click to change text', { title: 'Column 1', _width: 185, _height: 140, x: 10, y: 80, _fontSize: '14pt' }); + const content2 = Docs.Create.TextDocument('Click to change text', { title: 'Column 2', _width: 185, _height: 140, x: 205, y: 80, _fontSize: '14pt' }); switch (layout) { case 'blank': - doc = Docs.Create.FreeformDocument([], { title: input ? input : "Blank slide", _width: 400, _height: 225, x: x, y: y }); + doc = Docs.Create.FreeformDocument([], { title: input ? input : 'Blank slide', _width: 400, _height: 225, x: x, y: y }); break; case 'title': - doc = Docs.Create.FreeformDocument([title, subtitle], { title: input ? input : "Title slide", _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y }); + doc = Docs.Create.FreeformDocument([title, subtitle], { title: input ? input : 'Title slide', _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y }); break; case 'header': - doc = Docs.Create.FreeformDocument([header], { title: input ? input : "Section header", _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y }); + doc = Docs.Create.FreeformDocument([header], { title: input ? input : 'Section header', _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y }); break; case 'content': - doc = Docs.Create.FreeformDocument([contentTitle, content], { title: input ? input : "Title and content", _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y }); + doc = Docs.Create.FreeformDocument([contentTitle, content], { title: input ? input : 'Title and content', _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y }); break; case 'twoColumns': - doc = Docs.Create.FreeformDocument([contentTitle, content1, content2], { title: input ? input : "Title and two columns", _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y }); + doc = Docs.Create.FreeformDocument([contentTitle, content1, content2], { title: input ? input : 'Title and two columns', _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y }); break; default: break; } return doc; - } + }; // Dropdown that appears when the user wants to begin presenting (either minimize or sidebar view) @computed get presentDropdown() { return ( - <div className={`dropdown-play ${this.presentTools ? "active" : ""}`} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> - <div className="dropdown-play-button" onClick={undoBatch(action(() => { this.updateMinimize(); this.turnOffEdit(true); this.gotoDocument(this.itemIndex, this.activeItem); }))}> + <div className={`dropdown-play ${this.presentTools ? 'active' : ''}`} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> + <div + className="dropdown-play-button" + onClick={undoBatch( + action(() => { + this.updateMinimize(); + this.turnOffEdit(true); + this.gotoDocument(this.itemIndex, this.activeItem); + }) + )}> Mini-player </div> - <div className="dropdown-play-button" onClick={undoBatch(action(() => { this.layoutDoc.presStatus = "manual"; this.turnOffEdit(true); this.gotoDocument(this.itemIndex, this.activeItem); }))}> + <div + className="dropdown-play-button" + onClick={undoBatch( + action(() => { + this.layoutDoc.presStatus = 'manual'; + this.turnOffEdit(true); + this.gotoDocument(this.itemIndex, this.activeItem); + }) + )}> Sidebar player </div> </div> @@ -1835,7 +2194,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { @action nextKeyframe = (tagDoc: Doc, curDoc: Doc): void => { const childDocs = DocListCast(tagDoc[Doc.LayoutFieldKey(tagDoc)]); - const currentFrame = Cast(tagDoc._currentFrame, "number", null); + const currentFrame = Cast(tagDoc._currentFrame, 'number', null); if (currentFrame === undefined) { tagDoc._currentFrame = 0; // CollectionFreeFormDocumentView.setupScroll(tagDoc, 0); @@ -1845,19 +2204,19 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0, tagDoc); tagDoc._currentFrame = Math.max(0, (currentFrame || 0) + 1); tagDoc.lastFrame = Math.max(NumCast(tagDoc._currentFrame), NumCast(tagDoc.lastFrame)); - } + }; @action prevKeyframe = (tagDoc: Doc, actItem: Doc): void => { const childDocs = DocListCast(tagDoc[Doc.LayoutFieldKey(tagDoc)]); - const currentFrame = Cast(tagDoc._currentFrame, "number", null); + const currentFrame = Cast(tagDoc._currentFrame, 'number', null); if (currentFrame === undefined) { tagDoc._currentFrame = 0; // CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0); } CollectionFreeFormDocumentView.gotoKeyframe(childDocs.slice()); tagDoc._currentFrame = Math.max(0, (currentFrame || 0) - 1); - } + }; /** * Returns the collection type as a string for headers @@ -1868,15 +2227,33 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { let type: string = ''; if (activeItem) { switch (targetDoc.type) { - case DocumentType.PDF: type = "PDF"; break; - case DocumentType.RTF: type = "Text node"; break; - case DocumentType.COL: type = "Collection"; break; - case DocumentType.AUDIO: type = "Audio"; break; - case DocumentType.VID: type = "Video"; break; - case DocumentType.IMG: type = "Image"; break; - case DocumentType.WEB: type = "Web page"; break; - case DocumentType.MAP: type = "Map"; break; - default: type = "Other node"; break; + case DocumentType.PDF: + type = 'PDF'; + break; + case DocumentType.RTF: + type = 'Text node'; + break; + case DocumentType.COL: + type = 'Collection'; + break; + case DocumentType.AUDIO: + type = 'Audio'; + break; + case DocumentType.VID: + type = 'Video'; + break; + case DocumentType.IMG: + type = 'Image'; + break; + case DocumentType.WEB: + type = 'Web page'; + break; + case DocumentType.MAP: + type = 'Map'; + break; + default: + type = 'Other node'; + break; } } return type; @@ -1885,66 +2262,122 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { @observable private openActiveColorPicker: boolean = false; @observable private openViewedColorPicker: boolean = false; - - @computed get progressivizeDropdown() { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; if (activeItem && targetDoc) { - const activeFontColor = targetDoc["pres-text-color"] ? StrCast(targetDoc["pres-text-color"]) : "Black"; - const viewedFontColor = targetDoc["pres-text-viewed-color"] ? StrCast(targetDoc["pres-text-viewed-color"]) : "Black"; + const activeFontColor = targetDoc['pres-text-color'] ? StrCast(targetDoc['pres-text-color']) : 'Black'; + const viewedFontColor = targetDoc['pres-text-viewed-color'] ? StrCast(targetDoc['pres-text-viewed-color']) : 'Black'; return ( <div> - <div className={`presBox-ribbon ${this.progressivizeTools && this.layoutDoc.presStatus === "edit" ? "active" : ""}`} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> + <div + className={`presBox-ribbon ${this.progressivizeTools && this.layoutDoc.presStatus === 'edit' ? 'active' : ''}`} + onClick={e => e.stopPropagation()} + onPointerUp={e => e.stopPropagation()} + onPointerDown={e => e.stopPropagation()}> <div className="ribbon-box"> {this.stringType} selected - <div className="ribbon-doubleButton" style={{ borderTop: 'solid 1px darkgrey', display: (targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform') || targetDoc.type === DocumentType.IMG || targetDoc.type === DocumentType.RTF ? "inline-flex" : "none" }}> - <div className="ribbon-toggle" style={{ backgroundColor: activeItem.presProgressivize ? Colors.LIGHT_BLUE : "" }} onClick={this.progressivizeChild}>Contents</div> - <div className="ribbon-toggle" style={{ opacity: activeItem.presProgressivize ? 1 : 0.4, backgroundColor: targetDoc.editProgressivize ? Colors.LIGHT_BLUE : "" }} onClick={this.editProgressivize}>Edit</div> + <div + className="ribbon-doubleButton" + style={{ + borderTop: 'solid 1px darkgrey', + display: (targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform') || targetDoc.type === DocumentType.IMG || targetDoc.type === DocumentType.RTF ? 'inline-flex' : 'none', + }}> + <div className="ribbon-toggle" style={{ backgroundColor: activeItem.presProgressivize ? Colors.LIGHT_BLUE : '' }} onClick={this.progressivizeChild}> + Contents + </div> + <div className="ribbon-toggle" style={{ opacity: activeItem.presProgressivize ? 1 : 0.4, backgroundColor: targetDoc.editProgressivize ? Colors.LIGHT_BLUE : '' }} onClick={this.editProgressivize}> + Edit + </div> </div> - <div className="ribbon-doubleButton" style={{ display: activeItem.presProgressivize ? "inline-flex" : "none" }}> + <div className="ribbon-doubleButton" style={{ display: activeItem.presProgressivize ? 'inline-flex' : 'none' }}> <div className="presBox-subheading">Active text color</div> - <div className="ribbon-colorBox" style={{ backgroundColor: activeFontColor, height: 15, width: 15 }} onClick={action(() => { this.openActiveColorPicker = !this.openActiveColorPicker; })}> - </div> + <div + className="ribbon-colorBox" + style={{ backgroundColor: activeFontColor, height: 15, width: 15 }} + onClick={action(() => { + this.openActiveColorPicker = !this.openActiveColorPicker; + })}></div> </div> {this.activeColorPicker} - <div className="ribbon-doubleButton" style={{ display: activeItem.presProgressivize ? "inline-flex" : "none" }}> + <div className="ribbon-doubleButton" style={{ display: activeItem.presProgressivize ? 'inline-flex' : 'none' }}> <div className="presBox-subheading">Viewed font color</div> - <div className="ribbon-colorBox" style={{ backgroundColor: viewedFontColor, height: 15, width: 15 }} onClick={action(() => this.openViewedColorPicker = !this.openViewedColorPicker)}> - </div> + <div className="ribbon-colorBox" style={{ backgroundColor: viewedFontColor, height: 15, width: 15 }} onClick={action(() => (this.openViewedColorPicker = !this.openViewedColorPicker))}></div> </div> {this.viewedColorPicker} - <div className="ribbon-doubleButton" style={{ borderTop: 'solid 1px darkgrey', display: (targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform') || targetDoc.type === DocumentType.IMG ? "inline-flex" : "none" }}> - <div className="ribbon-toggle" style={{ backgroundColor: activeItem.zoomProgressivize ? Colors.LIGHT_BLUE : "" }} onClick={this.progressivizeZoom}>Zoom</div> - <div className="ribbon-toggle" style={{ opacity: activeItem.zoomProgressivize ? 1 : 0.4, backgroundColor: activeItem.editZoomProgressivize ? Colors.LIGHT_BLUE : "" }} onClick={this.editZoomProgressivize}>Edit</div> + <div + className="ribbon-doubleButton" + style={{ borderTop: 'solid 1px darkgrey', display: (targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform') || targetDoc.type === DocumentType.IMG ? 'inline-flex' : 'none' }}> + <div className="ribbon-toggle" style={{ backgroundColor: activeItem.zoomProgressivize ? Colors.LIGHT_BLUE : '' }} onClick={this.progressivizeZoom}> + Zoom + </div> + <div className="ribbon-toggle" style={{ opacity: activeItem.zoomProgressivize ? 1 : 0.4, backgroundColor: activeItem.editZoomProgressivize ? Colors.LIGHT_BLUE : '' }} onClick={this.editZoomProgressivize}> + Edit + </div> </div> - <div className="ribbon-doubleButton" style={{ borderTop: 'solid 1px darkgrey', display: targetDoc._viewType === "stacking" || targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF ? "inline-flex" : "none" }}> - <div className="ribbon-toggle" style={{ backgroundColor: activeItem.scrollProgressivize ? Colors.LIGHT_BLUE : "" }} onClick={this.progressivizeScroll}>Scroll</div> - <div className="ribbon-toggle" style={{ opacity: activeItem.scrollProgressivize ? 1 : 0.4, backgroundColor: targetDoc.editScrollProgressivize ? Colors.LIGHT_BLUE : "" }} onClick={this.editScrollProgressivize}>Edit</div> + <div + className="ribbon-doubleButton" + style={{ + borderTop: 'solid 1px darkgrey', + display: targetDoc._viewType === 'stacking' || targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF ? 'inline-flex' : 'none', + }}> + <div className="ribbon-toggle" style={{ backgroundColor: activeItem.scrollProgressivize ? Colors.LIGHT_BLUE : '' }} onClick={this.progressivizeScroll}> + Scroll + </div> + <div className="ribbon-toggle" style={{ opacity: activeItem.scrollProgressivize ? 1 : 0.4, backgroundColor: targetDoc.editScrollProgressivize ? Colors.LIGHT_BLUE : '' }} onClick={this.editScrollProgressivize}> + Edit + </div> </div> </div> <div className="ribbon-final-box"> Frames <div className="ribbon-doubleButton"> <div className="ribbon-frameSelector"> - <div key="back" title="back frame" className="backKeyframe" onClick={e => { e.stopPropagation(); this.prevKeyframe(targetDoc, activeItem); }}> - <FontAwesomeIcon icon={"caret-left"} size={"lg"} /> + <div + key="back" + title="back frame" + className="backKeyframe" + onClick={e => { + e.stopPropagation(); + this.prevKeyframe(targetDoc, activeItem); + }}> + <FontAwesomeIcon icon={'caret-left'} size={'lg'} /> </div> - <div key="num" title="toggle view all" className="numKeyframe" style={{ color: targetDoc.keyFrameEditing ? "white" : "black", backgroundColor: targetDoc.keyFrameEditing ? Colors.MEDIUM_BLUE : Colors.LIGHT_BLUE }} - onClick={action(() => targetDoc.keyFrameEditing = !targetDoc.keyFrameEditing)} > + <div + key="num" + title="toggle view all" + className="numKeyframe" + style={{ color: targetDoc.keyFrameEditing ? 'white' : 'black', backgroundColor: targetDoc.keyFrameEditing ? Colors.MEDIUM_BLUE : Colors.LIGHT_BLUE }} + onClick={action(() => (targetDoc.keyFrameEditing = !targetDoc.keyFrameEditing))}> {NumCast(targetDoc._currentFrame)} </div> - <div key="fwd" title="forward frame" className="fwdKeyframe" onClick={e => { e.stopPropagation(); this.nextKeyframe(targetDoc, activeItem); }}> - <FontAwesomeIcon icon={"caret-right"} size={"lg"} /> + <div + key="fwd" + title="forward frame" + className="fwdKeyframe" + onClick={e => { + e.stopPropagation(); + this.nextKeyframe(targetDoc, activeItem); + }}> + <FontAwesomeIcon icon={'caret-right'} size={'lg'} /> </div> </div> - <Tooltip title={<><div className="dash-tooltip">{"Last frame"}</div></>}><div className="ribbon-property">{NumCast(targetDoc.lastFrame)}</div></Tooltip> + <Tooltip + title={ + <> + <div className="dash-tooltip">{'Last frame'}</div> + </> + }> + <div className="ribbon-property">{NumCast(targetDoc.lastFrame)}</div> + </Tooltip> </div> <div className="ribbon-frameList"> {this.frameListHeader} {this.frameList} </div> - <div className="ribbon-toggle" style={{ height: 20, backgroundColor: Colors.LIGHT_BLUE }} onClick={() => console.log(" TODO: play frames")}>Play</div> + <div className="ribbon-toggle" style={{ height: 20, backgroundColor: Colors.LIGHT_BLUE }} onClick={() => console.log(' TODO: play frames')}> + Play + </div> </div> </div> </div> @@ -1958,37 +2391,41 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; const val = String(color.hex); - targetDoc["pres-text-color"] = val; + targetDoc['pres-text-color'] = val; return true; - } + }; @undoBatch @action switchPresented = (color: ColorState) => { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; const val = String(color.hex); - targetDoc["pres-text-viewed-color"] = val; + targetDoc['pres-text-viewed-color'] = val; return true; - } + }; @computed get activeColorPicker() { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; - return !this.openActiveColorPicker ? (null) : <SketchPicker onChange={this.switchActive} - presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', - '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', - '#FFFFFF', '#f1efeb', 'transparent']} - color={StrCast(targetDoc["pres-text-color"])} />; + return !this.openActiveColorPicker ? null : ( + <SketchPicker + onChange={this.switchActive} + presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']} + color={StrCast(targetDoc['pres-text-color'])} + /> + ); } @computed get viewedColorPicker() { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; - return !this.openViewedColorPicker ? (null) : <SketchPicker onChange={this.switchPresented} - presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', - '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', - '#FFFFFF', '#f1efeb', 'transparent']} - color={StrCast(targetDoc["pres-text-viewed-color"])} />; + return !this.openViewedColorPicker ? null : ( + <SketchPicker + onChange={this.switchPresented} + presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']} + color={StrCast(targetDoc['pres-text-viewed-color'])} + /> + ); } @action @@ -1999,7 +2436,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { if (srcContext) this.togglePath(srcContext, true); } // Turn off the progressivize editors for each document - this.childDocs.forEach((doc) => { + this.childDocs.forEach(doc => { doc.editSnapZoomProgressivize = false; doc.editZoomProgressivize = false; const targetDoc = Cast(doc.presentationTargetDoc, Doc, null); @@ -2008,7 +2445,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { // targetDoc.editScrollProgressivize = false; } }); - } + }; //Toggle whether the user edits or not @action @@ -2016,14 +2453,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; if (!targetDoc.editZoomProgressivize) { - if (!activeItem.zoomProgressivize) activeItem.zoomProgressivize = true; targetDoc.zoomProgressivize = true; + if (!activeItem.zoomProgressivize) activeItem.zoomProgressivize = true; + targetDoc.zoomProgressivize = true; targetDoc.editZoomProgressivize = true; activeItem.editZoomProgressivize = true; } else { targetDoc.editZoomProgressivize = false; activeItem.editZoomProgressivize = false; } - } + }; //Toggle whether the user edits or not @action @@ -2031,12 +2469,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; if (!targetDoc.editScrollProgressivize) { - if (!targetDoc.scrollProgressivize) { targetDoc.scrollProgressivize = true; activeItem.scrollProgressivize = true; } + if (!targetDoc.scrollProgressivize) { + targetDoc.scrollProgressivize = true; + activeItem.scrollProgressivize = true; + } targetDoc.editScrollProgressivize = true; } else { targetDoc.editScrollProgressivize = false; } - } + }; //Progressivize Zoom @action @@ -2052,7 +2493,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { targetDoc._currentFrame = 0; targetDoc.lastFrame = 0; } - } + }; //Progressivize Zoom @action @@ -2068,7 +2509,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { targetDoc._currentFrame = 0; targetDoc.lastFrame = 0; } - } + }; //Progressivize Child Docs @action @@ -2077,12 +2518,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const targetDoc: Doc = this.targetDoc; targetDoc._currentFrame = targetDoc.lastFrame; if (!targetDoc.editProgressivize) { - if (!activeItem.presProgressivize) { activeItem.presProgressivize = true; targetDoc.presProgressivize = true; } + if (!activeItem.presProgressivize) { + activeItem.presProgressivize = true; + targetDoc.presProgressivize = true; + } targetDoc.editProgressivize = true; } else { targetDoc.editProgressivize = false; } - } + }; @action progressivizeChild = (e: React.MouseEvent) => { @@ -2104,40 +2548,48 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { targetDoc._currentFrame = 0; targetDoc.keyFrameEditing = true; } - } + }; @action checkMovementLists = (doc: Doc, xlist: any, ylist: any) => { const x: List<number> = xlist; const y: List<number> = ylist; const tags: JSX.Element[] = []; - let pathPoints = ""; //List of all of the pathpoints that need to be added + let pathPoints = ''; //List of all of the pathpoints that need to be added for (let i = 0; i < x.length - 1; i++) { if (y[i] || x[i]) { - if (i === 0) pathPoints = (x[i] - 11) + "," + (y[i] + 33); - else pathPoints = pathPoints + " " + (x[i] - 11) + "," + (y[i] + 33); - tags.push(<div className="progressivizeMove-frame" style={{ position: 'absolute', top: y[i], left: x[i] }}>{i}</div>); + if (i === 0) pathPoints = x[i] - 11 + ',' + (y[i] + 33); + else pathPoints = pathPoints + ' ' + (x[i] - 11) + ',' + (y[i] + 33); + tags.push( + <div className="progressivizeMove-frame" style={{ position: 'absolute', top: y[i], left: x[i] }}> + {i} + </div> + ); } } - tags.push(<svg style={{ overflow: 'visible', position: 'absolute' }}><polyline - points={pathPoints} - style={{ - position: 'absolute', - opacity: 1, - stroke: "#000000", - strokeWidth: 2, - strokeDasharray: '10 5', - }} - fill="none" - /></svg>); + tags.push( + <svg style={{ overflow: 'visible', position: 'absolute' }}> + <polyline + points={pathPoints} + style={{ + position: 'absolute', + opacity: 1, + stroke: '#000000', + strokeWidth: 2, + strokeDasharray: '10 5', + }} + fill="none" + /> + </svg> + ); return tags; - } + }; @observable toggleDisplayMovement = (doc: Doc) => { if (doc.displayMovement) doc.displayMovement = false; else doc.displayMovement = true; - } + }; @action checkList = (doc: Doc, list: any): number => { @@ -2149,22 +2601,54 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { x[NumCast(doc._currentFrame)] = x[NumCast(doc._currentFrame) - 1]; return x[NumCast(doc._currentFrame)]; } else return 100; - } + }; @computed get progressivizeChildDocs() { const targetDoc: Doc = this.targetDoc; const docs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]); const tags: JSX.Element[] = []; docs.forEach((doc, index) => { - if (doc["x-indexed"] && doc["y-indexed"]) { - tags.push(<div style={{ position: 'absolute', display: doc.displayMovement ? "block" : "none" }}>{this.checkMovementLists(doc, doc["x-indexed"], doc["y-indexed"])}</div>); + if (doc['x-indexed'] && doc['y-indexed']) { + tags.push(<div style={{ position: 'absolute', display: doc.displayMovement ? 'block' : 'none' }}>{this.checkMovementLists(doc, doc['x-indexed'], doc['y-indexed'])}</div>); } tags.push( - <div className="progressivizeButton" key={index} onPointerLeave={() => { if (NumCast(targetDoc._currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0; }} onPointerOver={() => { if (NumCast(targetDoc._currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0.5; }} onClick={e => { this.toggleDisplayMovement(doc); e.stopPropagation(); }} style={{ backgroundColor: doc.displayMovement ? Colors.LIGHT_BLUE : "#c8c8c8", top: NumCast(doc.y), left: NumCast(doc.x) }}> - <div className="progressivizeButton-prev"><FontAwesomeIcon icon={"caret-left"} size={"lg"} onClick={e => { e.stopPropagation(); this.prevAppearFrame(doc, index); }} /></div> - <div className="progressivizeButton-frame">{doc.appearFrame}</div> - <div className="progressivizeButton-next"><FontAwesomeIcon icon={"caret-right"} size={"lg"} onClick={e => { e.stopPropagation(); this.nextAppearFrame(doc, index); }} /></div> - </div>); + <div + className="progressivizeButton" + key={index} + onPointerLeave={() => { + if (NumCast(targetDoc._currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0; + }} + onPointerOver={() => { + if (NumCast(targetDoc._currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0.5; + }} + onClick={e => { + this.toggleDisplayMovement(doc); + e.stopPropagation(); + }} + style={{ backgroundColor: doc.displayMovement ? Colors.LIGHT_BLUE : '#c8c8c8', top: NumCast(doc.y), left: NumCast(doc.x) }}> + <div className="progressivizeButton-prev"> + <FontAwesomeIcon + icon={'caret-left'} + size={'lg'} + onClick={e => { + e.stopPropagation(); + this.prevAppearFrame(doc, index); + }} + /> + </div> + <div className="progressivizeButton-frame">{NumCast(doc.appearFrame)}</div> + <div className="progressivizeButton-next"> + <FontAwesomeIcon + icon={'caret-right'} + size={'lg'} + onClick={e => { + e.stopPropagation(); + this.nextAppearFrame(doc, index); + }} + /> + </div> + </div> + ); }); return tags; } @@ -2173,25 +2657,25 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { nextAppearFrame = (doc: Doc, i: number): void => { // const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); // const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); - const appearFrame = Cast(doc.appearFrame, "number", null); + const appearFrame = Cast(doc.appearFrame, 'number', null); if (appearFrame === undefined) { doc.appearFrame = 0; } doc.appearFrame = appearFrame + 1; - this.updateOpacityList(doc["opacity-indexed"], NumCast(doc.appearFrame)); - } + this.updateOpacityList(doc['opacity-indexed'], NumCast(doc.appearFrame)); + }; @action prevAppearFrame = (doc: Doc, i: number): void => { // const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); // const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); - const appearFrame = Cast(doc.appearFrame, "number", null); + const appearFrame = Cast(doc.appearFrame, 'number', null); if (appearFrame === undefined) { doc.appearFrame = 0; } doc.appearFrame = Math.max(0, appearFrame - 1); - this.updateOpacityList(doc["opacity-indexed"], NumCast(doc.appearFrame)); - } + this.updateOpacityList(doc['opacity-indexed'], NumCast(doc.appearFrame)); + }; @action updateOpacityList = (list: any, frame: number) => { @@ -2216,10 +2700,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { } list = x; } - } + }; @computed get moreInfoDropdown() { - return (<div></div>); + return <div></div>; } @computed @@ -2235,28 +2719,36 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { } else { CurrentUserUtils.propertiesWidth = 250; } - } + }; @computed get toolbar() { - const propIcon = CurrentUserUtils.propertiesWidth > 0 ? "angle-double-right" : "angle-double-left"; - const propTitle = CurrentUserUtils.propertiesWidth > 0 ? "Close Presentation Panel" : "Open Presentation Panel"; + const propIcon = CurrentUserUtils.propertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left'; + const propTitle = CurrentUserUtils.propertiesWidth > 0 ? 'Close Presentation Panel' : 'Open Presentation Panel'; const mode = StrCast(this.rootDoc._viewType) as CollectionViewType; const isMini: boolean = this.toolbarWidth <= 100; - const presKeyEvents: boolean = (this.isPres && this._presKeyEventsActive && this.rootDoc === CurrentUserUtils.ActivePresentation); + const presKeyEvents: boolean = this.isPres && this._presKeyEventsActive && this.rootDoc === CurrentUserUtils.ActivePresentation; const activeColor = Colors.LIGHT_BLUE; const inactiveColor = Colors.WHITE; - return (mode === CollectionViewType.Carousel3D) ? (null) : ( + return mode === CollectionViewType.Carousel3D ? null : ( <div id="toolbarContainer" className={'presBox-toolbar'}> {/* <Tooltip title={<><div className="dash-tooltip">{"Add new slide"}</div></>}><div className={`toolbar-button ${this.newDocumentTools ? "active" : ""}`} onClick={action(() => this.newDocumentTools = !this.newDocumentTools)}> <FontAwesomeIcon icon={"plus"} /> <FontAwesomeIcon className={`dropdown ${this.newDocumentTools ? "active" : ""}`} icon={"angle-down"} /> </div></Tooltip> */} - <Tooltip title={<><div className="dash-tooltip">{"View paths"}</div></>}> - <div style={{ opacity: this.childDocs.length > 1 && this.layoutDoc.presCollection ? 1 : 0.3, color: this._pathBoolean ? Colors.MEDIUM_BLUE : 'white', width: isMini ? "100%" : undefined }} className={"toolbar-button"} onClick={this.childDocs.length > 1 && this.layoutDoc.presCollection ? this.viewPaths : undefined}> - <FontAwesomeIcon icon={"exchange-alt"} /> + <Tooltip + title={ + <> + <div className="dash-tooltip">{'View paths'}</div> + </> + }> + <div + style={{ opacity: this.childDocs.length > 1 && this.layoutDoc.presCollection ? 1 : 0.3, color: this._pathBoolean ? Colors.MEDIUM_BLUE : 'white', width: isMini ? '100%' : undefined }} + className={'toolbar-button'} + onClick={this.childDocs.length > 1 && this.layoutDoc.presCollection ? this.viewPaths : undefined}> + <FontAwesomeIcon icon={'exchange-alt'} /> </div> </Tooltip> - {isMini ? (null) : + {isMini ? null : ( <> <div className="toolbar-divider" /> {/* <Tooltip title={<><div className="dash-tooltip">{this._expandBoolean ? "Minimize all" : "Expand all"}</div></>}> @@ -2267,18 +2759,28 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { </div> </Tooltip> <div className="toolbar-divider" /> */} - <Tooltip title={<><div className="dash-tooltip">{presKeyEvents ? "Keys are active" : "Keys are not active - click anywhere on the presentation trail to activate keys"}</div></>}> + <Tooltip + title={ + <> + <div className="dash-tooltip">{presKeyEvents ? 'Keys are active' : 'Keys are not active - click anywhere on the presentation trail to activate keys'}</div> + </> + }> <div className="toolbar-button" style={{ cursor: presKeyEvents ? 'default' : 'pointer', position: 'absolute', right: 30, fontSize: 16 }}> - <FontAwesomeIcon className={"toolbar-thumbtack"} icon={"keyboard"} style={{ color: presKeyEvents ? activeColor : inactiveColor }} /> + <FontAwesomeIcon className={'toolbar-thumbtack'} icon={'keyboard'} style={{ color: presKeyEvents ? activeColor : inactiveColor }} /> </div> </Tooltip> - <Tooltip title={<><div className="dash-tooltip">{propTitle}</div></>}> + <Tooltip + title={ + <> + <div className="dash-tooltip">{propTitle}</div> + </> + }> <div className="toolbar-button" style={{ position: 'absolute', right: 4, fontSize: 16 }} onClick={this.toggleProperties}> - <FontAwesomeIcon className={"toolbar-thumbtack"} icon={propIcon} style={{ color: CurrentUserUtils.propertiesWidth > 0 ? activeColor : inactiveColor }} /> + <FontAwesomeIcon className={'toolbar-thumbtack'} icon={propIcon} style={{ color: CurrentUserUtils.propertiesWidth > 0 ? activeColor : inactiveColor }} /> </div> </Tooltip> </> - } + )} </div> ); } @@ -2292,35 +2794,45 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const mode = StrCast(this.rootDoc._viewType) as CollectionViewType; const isMini: boolean = this.toolbarWidth <= 100; return ( - <div className="presBox-buttons" style={{ display: !this.rootDoc._chromeHidden ? "none" : undefined }}> - {isMini ? (null) : <select className="presBox-viewPicker" - style={{ display: this.layoutDoc.presStatus === "edit" ? "block" : "none" }} - onPointerDown={e => e.stopPropagation()} - onChange={this.viewChanged} - value={mode}> - <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Stacking}>List</option> - <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Tree}>Tree</option> - {Doc.noviceMode ? (null) : <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Carousel3D}>3D Carousel</option>} - </select>} + <div className="presBox-buttons" style={{ display: !this.rootDoc._chromeHidden ? 'none' : undefined }}> + {isMini ? null : ( + <select className="presBox-viewPicker" style={{ display: this.layoutDoc.presStatus === 'edit' ? 'block' : 'none' }} onPointerDown={e => e.stopPropagation()} onChange={this.viewChanged} value={mode}> + <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Stacking}> + List + </option> + <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Tree}> + Tree + </option> + {Doc.noviceMode ? null : ( + <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Carousel3D}> + 3D Carousel + </option> + )} + </select> + )} <div className="presBox-presentPanel" style={{ opacity: this.childDocs.length ? 1 : 0.3 }}> - <span className={`presBox-button ${this.layoutDoc.presStatus === "edit" ? "present" : ""}`}> - <div className="presBox-button-left" + <span className={`presBox-button ${this.layoutDoc.presStatus === 'edit' ? 'present' : ''}`}> + <div + className="presBox-button-left" onClick={undoBatch(() => { if (this.childDocs.length) { - this.layoutDoc.presStatus = "manual"; + this.layoutDoc.presStatus = 'manual'; this.gotoDocument(this.itemIndex, this.activeItem); } })}> - <FontAwesomeIcon icon={"play-circle"} /> - <div style={{ display: this.props.PanelWidth() > 200 ? "inline-flex" : "none" }}> Present</div> + <FontAwesomeIcon icon={'play-circle'} /> + <div style={{ display: this.props.PanelWidth() > 200 ? 'inline-flex' : 'none' }}> Present</div> </div> - {(mode === CollectionViewType.Carousel3D || isMini) ? (null) : <div className={`presBox-button-right ${this.presentTools ? "active" : ""}`} - onClick={(action(() => { - if (this.childDocs.length) this.presentTools = !this.presentTools; - }))}> - <FontAwesomeIcon className="dropdown" style={{ margin: 0, transform: this.presentTools ? 'rotate(180deg)' : 'rotate(0deg)' }} icon={"angle-down"} /> - {this.presentDropdown} - </div>} + {mode === CollectionViewType.Carousel3D || isMini ? null : ( + <div + className={`presBox-button-right ${this.presentTools ? 'active' : ''}`} + onClick={action(() => { + if (this.childDocs.length) this.presentTools = !this.presentTools; + })}> + <FontAwesomeIcon className="dropdown" style={{ margin: 0, transform: this.presentTools ? 'rotate(180deg)' : 'rotate(0deg)' }} icon={'angle-down'} /> + {this.presentDropdown} + </div> + )} </span> {this.playButtons} </div> @@ -2332,7 +2844,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { getList = (list: any): List<number> => { const x: List<number> = list; return x; - } + }; @action updateList = (list: any): List<number> => { @@ -2341,7 +2853,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { x.length + 1; x[x.length - 1] = NumCast(targetDoc._scrollY); return x; - } + }; @action newFrame = () => { @@ -2350,7 +2862,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const type: string = StrCast(targetDoc.type); if (!activeItem.frameList) activeItem.frameList = new List<number>(); switch (type) { - case (DocumentType.PDF || DocumentType.RTF || DocumentType.WEB): + case DocumentType.PDF || DocumentType.RTF || DocumentType.WEB: this.updateList(activeItem.frameList); break; case DocumentType.COL: @@ -2358,20 +2870,46 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { default: break; } - } + }; @computed get frameListHeader() { - return (<div className="frameList-header"> - Frames {this.panable ? <i>Panable</i> : this.scrollable ? <i>Scrollable</i> : (null)} - <div className={"frameList-headerButtons"}> - <Tooltip title={<><div className="dash-tooltip">{"Add frame by example"}</div></>}><div className={"headerButton"} onClick={e => { e.stopPropagation(); this.newFrame(); }}> - <FontAwesomeIcon icon={"plus"} onPointerDown={e => e.stopPropagation()} /> - </div></Tooltip> - <Tooltip title={<><div className="dash-tooltip">{"Edit in collection"}</div></>}><div className={"headerButton"} onClick={e => { e.stopPropagation(); console.log('New frame'); }}> - <FontAwesomeIcon icon={"edit"} onPointerDown={e => e.stopPropagation()} /> - </div></Tooltip> + return ( + <div className="frameList-header"> + Frames {this.panable ? <i>Panable</i> : this.scrollable ? <i>Scrollable</i> : null} + <div className={'frameList-headerButtons'}> + <Tooltip + title={ + <> + <div className="dash-tooltip">{'Add frame by example'}</div> + </> + }> + <div + className={'headerButton'} + onClick={e => { + e.stopPropagation(); + this.newFrame(); + }}> + <FontAwesomeIcon icon={'plus'} onPointerDown={e => e.stopPropagation()} /> + </div> + </Tooltip> + <Tooltip + title={ + <> + <div className="dash-tooltip">{'Edit in collection'}</div> + </> + }> + <div + className={'headerButton'} + onClick={e => { + e.stopPropagation(); + console.log('New frame'); + }}> + <FontAwesomeIcon icon={'edit'} onPointerDown={e => e.stopPropagation()} /> + </div> + </Tooltip> + </div> </div> - </div>); + ); } @computed get frameList() { @@ -2379,66 +2917,118 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const targetDoc: Doc = this.targetDoc; const frameList: List<number> = this.getList(activeItem.frameList); if (frameList) { - const frameItems = frameList.map((value) => - <div className="framList-item"> - - </div> - ); - return ( - - <div className="frameList-container"> - {frameItems} - </div> - ); - } else return (null); - + const frameItems = frameList.map(value => <div className="framList-item"></div>); + return <div className="frameList-container">{frameItems}</div>; + } else return null; } @computed get playButtonFrames() { const targetDoc: Doc = this.targetDoc; return ( <> - {this.targetDoc ? <div className="presPanel-button-frame" style={{ display: targetDoc.lastFrame !== undefined && targetDoc.lastFrame >= 0 ? "inline-flex" : "none" }}> - <div>{targetDoc._currentFrame}</div> - <div className="presPanel-divider" style={{ border: 'solid 0.5px white', height: '60%' }}></div> - <div>{targetDoc.lastFrame}</div> - </div> : null} + {this.targetDoc ? ( + <div className="presPanel-button-frame" style={{ display: targetDoc.lastFrame !== undefined && targetDoc.lastFrame >= 0 ? 'inline-flex' : 'none' }}> + <div>{NumCast(targetDoc._currentFrame)}</div> + <div className="presPanel-divider" style={{ border: 'solid 0.5px white', height: '60%' }}></div> + <div>{NumCast(targetDoc.lastFrame)}</div> + </div> + ) : null} </> ); } @computed get playButtons() { - const presEnd: boolean = !this.layoutDoc.presLoop && (this.itemIndex === this.childDocs.length - 1); - const presStart: boolean = !this.layoutDoc.presLoop && (this.itemIndex === 0); + const presEnd: boolean = !this.layoutDoc.presLoop && this.itemIndex === this.childDocs.length - 1; + const presStart: boolean = !this.layoutDoc.presLoop && this.itemIndex === 0; // Case 1: There are still other frames and should go through all frames before going to next slide - return (<div className="presPanelOverlay" style={{ display: this.layoutDoc.presStatus !== "edit" ? "inline-flex" : "none" }}> - <Tooltip title={<><div className="dash-tooltip">{"Loop"}</div></>}><div className="presPanel-button" style={{ color: this.layoutDoc.presLoop ? Colors.MEDIUM_BLUE : 'white' }} onClick={() => this.layoutDoc.presLoop = !this.layoutDoc.presLoop}><FontAwesomeIcon icon={"redo-alt"} /></div></Tooltip> - <div className="presPanel-divider"></div> - <div className="presPanel-button" style={{ opacity: presStart ? 0.4 : 1 }} onClick={() => { this.back(); if (this._presTimer) { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; } }}><FontAwesomeIcon icon={"arrow-left"} /></div> - <Tooltip title={<><div className="dash-tooltip">{this.layoutDoc.presStatus === PresStatus.Autoplay ? "Pause" : "Autoplay"}</div></>}><div className="presPanel-button" onClick={this.startOrPause}><FontAwesomeIcon icon={this.layoutDoc.presStatus === PresStatus.Autoplay ? "pause" : "play"} /></div></Tooltip> - <div className="presPanel-button" style={{ opacity: presEnd ? 0.4 : 1 }} onClick={() => { this.next(); if (this._presTimer) { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; } }}><FontAwesomeIcon icon={"arrow-right"} /></div> - <div className="presPanel-divider"></div> - <Tooltip title={<><div className="dash-tooltip">{"Click to return to 1st slide"}</div></>}><div className="presPanel-button" style={{ border: 'solid 1px white' }} onClick={() => this.gotoDocument(0, this.activeItem)}><b>1</b></div></Tooltip> - <div - className="presPanel-button-text" - onClick={() => this.gotoDocument(0, this.activeItem)} - style={{ display: this.props.PanelWidth() > 250 ? "inline-flex" : "none" }}> - Slide {this.itemIndex + 1} / {this.childDocs.length} - {this.playButtonFrames} + return ( + <div className="presPanelOverlay" style={{ display: this.layoutDoc.presStatus !== 'edit' ? 'inline-flex' : 'none' }}> + <Tooltip + title={ + <> + <div className="dash-tooltip">{'Loop'}</div> + </> + }> + <div className="presPanel-button" style={{ color: this.layoutDoc.presLoop ? Colors.MEDIUM_BLUE : 'white' }} onClick={() => (this.layoutDoc.presLoop = !this.layoutDoc.presLoop)}> + <FontAwesomeIcon icon={'redo-alt'} /> + </div> + </Tooltip> + <div className="presPanel-divider"></div> + <div + className="presPanel-button" + style={{ opacity: presStart ? 0.4 : 1 }} + onClick={() => { + this.back(); + if (this._presTimer) { + clearTimeout(this._presTimer); + this.layoutDoc.presStatus = PresStatus.Manual; + } + }}> + <FontAwesomeIcon icon={'arrow-left'} /> + </div> + <Tooltip + title={ + <> + <div className="dash-tooltip">{this.layoutDoc.presStatus === PresStatus.Autoplay ? 'Pause' : 'Autoplay'}</div> + </> + }> + <div className="presPanel-button" onClick={this.startOrPause}> + <FontAwesomeIcon icon={this.layoutDoc.presStatus === PresStatus.Autoplay ? 'pause' : 'play'} /> + </div> + </Tooltip> + <div + className="presPanel-button" + style={{ opacity: presEnd ? 0.4 : 1 }} + onClick={() => { + this.next(); + if (this._presTimer) { + clearTimeout(this._presTimer); + this.layoutDoc.presStatus = PresStatus.Manual; + } + }}> + <FontAwesomeIcon icon={'arrow-right'} /> + </div> + <div className="presPanel-divider"></div> + <Tooltip + title={ + <> + <div className="dash-tooltip">{'Click to return to 1st slide'}</div> + </> + }> + <div className="presPanel-button" style={{ border: 'solid 1px white' }} onClick={() => this.gotoDocument(0, this.activeItem)}> + <b>1</b> + </div> + </Tooltip> + <div className="presPanel-button-text" onClick={() => this.gotoDocument(0, this.activeItem)} style={{ display: this.props.PanelWidth() > 250 ? 'inline-flex' : 'none' }}> + Slide {this.itemIndex + 1} / {this.childDocs.length} + {this.playButtonFrames} + </div> + <div className="presPanel-divider"></div> + {this.props.PanelWidth() > 250 ? ( + <div + className="presPanel-button-text" + onClick={undoBatch( + action(() => { + this.layoutDoc.presStatus = 'edit'; + clearTimeout(this._presTimer); + }) + )}> + EXIT + </div> + ) : ( + <div className="presPanel-button" onClick={undoBatch(action(() => (this.layoutDoc.presStatus = 'edit')))}> + <FontAwesomeIcon icon={'times'} /> + </div> + )} </div> - <div className="presPanel-divider"></div> - {this.props.PanelWidth() > 250 ? <div className="presPanel-button-text" onClick={undoBatch(action(() => { this.layoutDoc.presStatus = "edit"; clearTimeout(this._presTimer); }))}>EXIT</div> - : <div className="presPanel-button" onClick={undoBatch(action(() => this.layoutDoc.presStatus = "edit"))}> - <FontAwesomeIcon icon={"times"} /> - </div>} - </div>); + ); } @action startOrPause = () => { if (this.layoutDoc.presStatus === PresStatus.Manual || this.layoutDoc.presStatus === PresStatus.Edit) this.startAutoPres(this.itemIndex); else this.pauseAutoPres(); - } + }; @action prevClicked = (e: PointerEvent) => { @@ -2447,7 +3037,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; } - } + }; @action nextClicked = (e: PointerEvent) => { @@ -2456,35 +3046,35 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; } - } + }; @undoBatch @action exitClicked = (e: PointerEvent) => { this.updateMinimize(); this.layoutDoc.presStatus = PresStatus.Edit; clearTimeout(this._presTimer); - } + }; @action startMarqueeCreateSlide = () => { PresBox.startMarquee = true; - } + }; AddToMap = (treeViewDoc: Doc, index: number[]): Doc[] => { var indexNum = 0; for (let i = 0; i < index.length; i++) { - indexNum += (index[i] * (10 ** (-i))); + indexNum += index[i] * 10 ** -i; } if (this._treeViewMap.get(treeViewDoc) !== indexNum) { this._treeViewMap.set(treeViewDoc, indexNum); const sorted = this.sort(this._treeViewMap); const curList = DocListCast(this.dataDoc[this.presFieldKey]); - if (sorted.length !== curList.length || sorted.some((doc,ind) => doc !== curList[ind])) { + if (sorted.length !== curList.length || sorted.some((doc, ind) => doc !== curList[ind])) { this.dataDoc[this.presFieldKey] = new List<Doc>(sorted); // this is a flat array of Docs } } return this.childDocs; - } + }; RemFromMap = (treeViewDoc: Doc, index: number[]): Doc[] => { if (!this._unmounting && this.isTree) { @@ -2492,10 +3082,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { this.dataDoc[this.presFieldKey] = new List<Doc>(this.sort(this._treeViewMap)); } return this.childDocs; - } + }; // TODO: [AL] implement sort function for an array of numbers (e.g. arr[1,2,4] v arr[1,2,1]) - sort = (treeViewMap: Map<Doc, number>) => [...treeViewMap.entries()].sort((a: [Doc, number], b: [Doc, number]) => a[1] > b[1] ? 1 : a[1] < b[1] ? -1 : 0).map(kv => kv[0]); + sort = (treeViewMap: Map<Doc, number>) => [...treeViewMap.entries()].sort((a: [Doc, number], b: [Doc, number]) => (a[1] > b[1] ? 1 : a[1] < b[1] ? -1 : 0)).map(kv => kv[0]); render() { // calling this method for keyEvents @@ -2503,42 +3093,73 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { // needed to ensure that the childDocs are loaded for looking up fields this.childDocs.slice(); const mode = StrCast(this.rootDoc._viewType) as CollectionViewType; - const presKeyEvents: boolean = (this.isPres && this._presKeyEventsActive && this.rootDoc === CurrentUserUtils.ActivePresentation); - const presEnd: boolean = !this.layoutDoc.presLoop && (this.itemIndex === this.childDocs.length - 1); - const presStart: boolean = !this.layoutDoc.presLoop && (this.itemIndex === 0); - return DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.rootDoc) ? + const presKeyEvents: boolean = this.isPres && this._presKeyEventsActive && this.rootDoc === CurrentUserUtils.ActivePresentation; + const presEnd: boolean = !this.layoutDoc.presLoop && this.itemIndex === this.childDocs.length - 1; + const presStart: boolean = !this.layoutDoc.presLoop && this.itemIndex === 0; + return DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.rootDoc) ? ( <div className="miniPres" onClick={e => e.stopPropagation()}> - <div className="presPanelOverlay" style={{ display: "inline-flex", height: 30, background: '#323232', top: 0, zIndex: 3000000, boxShadow: presKeyEvents ? '0 0 0px 3px ' + Colors.MEDIUM_BLUE : undefined }}> - <Tooltip title={<><div className="dash-tooltip">{"Loop"}</div></>}><div className="presPanel-button" style={{ color: this.layoutDoc.presLoop ? Colors.MEDIUM_BLUE : undefined }} - onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, () => this.layoutDoc.presLoop = !this.layoutDoc.presLoop, false, false)}><FontAwesomeIcon icon={"redo-alt"} /></div></Tooltip> + <div className="presPanelOverlay" style={{ display: 'inline-flex', height: 30, background: '#323232', top: 0, zIndex: 3000000, boxShadow: presKeyEvents ? '0 0 0px 3px ' + Colors.MEDIUM_BLUE : undefined }}> + <Tooltip + title={ + <> + <div className="dash-tooltip">{'Loop'}</div> + </> + }> + <div + className="presPanel-button" + style={{ color: this.layoutDoc.presLoop ? Colors.MEDIUM_BLUE : undefined }} + onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, () => (this.layoutDoc.presLoop = !this.layoutDoc.presLoop), false, false)}> + <FontAwesomeIcon icon={'redo-alt'} /> + </div> + </Tooltip> <div className="presPanel-divider"></div> - <div className="presPanel-button" style={{ opacity: presStart ? 0.4 : 1 }} - onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, this.prevClicked, false, false)}><FontAwesomeIcon icon={"arrow-left"} /></div> - <Tooltip title={<><div className="dash-tooltip">{this.layoutDoc.presStatus === PresStatus.Autoplay ? "Pause" : "Autoplay"}</div></>}><div className="presPanel-button" - onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, this.startOrPause, false, false)}><FontAwesomeIcon icon={this.layoutDoc.presStatus === "auto" ? "pause" : "play"} /></div></Tooltip> - <div className="presPanel-button" style={{ opacity: presEnd ? 0.4 : 1 }} - onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, this.nextClicked, false, false)}><FontAwesomeIcon icon={"arrow-right"} /></div> + <div className="presPanel-button" style={{ opacity: presStart ? 0.4 : 1 }} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, this.prevClicked, false, false)}> + <FontAwesomeIcon icon={'arrow-left'} /> + </div> + <Tooltip + title={ + <> + <div className="dash-tooltip">{this.layoutDoc.presStatus === PresStatus.Autoplay ? 'Pause' : 'Autoplay'}</div> + </> + }> + <div className="presPanel-button" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, this.startOrPause, false, false)}> + <FontAwesomeIcon icon={this.layoutDoc.presStatus === 'auto' ? 'pause' : 'play'} /> + </div> + </Tooltip> + <div className="presPanel-button" style={{ opacity: presEnd ? 0.4 : 1 }} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, this.nextClicked, false, false)}> + <FontAwesomeIcon icon={'arrow-right'} /> + </div> <div className="presPanel-divider"></div> - <Tooltip title={<><div className="dash-tooltip">{"Click to return to 1st slide"}</div></>}><div className="presPanel-button" style={{ border: 'solid 1px white' }} - onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, () => this.gotoDocument(0, this.activeItem), false, false)}><b>1</b></div></Tooltip> + <Tooltip + title={ + <> + <div className="dash-tooltip">{'Click to return to 1st slide'}</div> + </> + }> + <div className="presPanel-button" style={{ border: 'solid 1px white' }} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, () => this.gotoDocument(0, this.activeItem), false, false)}> + <b>1</b> + </div> + </Tooltip> <div className="presPanel-button-text"> Slide {this.itemIndex + 1} / {this.childDocs.length} {this.playButtonFrames} </div> <div className="presPanel-divider" /> - <div className="presPanel-button-text" onPointerDown={e => - setupMoveUpEvents(this, e, returnFalse, returnFalse, this.exitClicked, false, false)}>EXIT</div> + <div className="presPanel-button-text" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, this.exitClicked, false, false)}> + EXIT + </div> </div> - </div > - : - <div className="presBox-cont" style={{ minWidth: DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.layoutDoc) ? 240 : undefined }} > + </div> + ) : ( + <div className="presBox-cont" style={{ minWidth: DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.layoutDoc) ? 240 : undefined }}> {this.topPanel} {this.toolbar} {this.newDocumentToolbarDropdown} <div className="presBox-listCont"> <div className="Slide" style={{ height: `calc(100% - 30px)` }}> - {mode !== CollectionViewType.Invalid ? - <CollectionView {...this.props} + {mode !== CollectionViewType.Invalid ? ( + <CollectionView + {...this.props} ContainingCollectionDoc={this.props.Document} PanelWidth={this.props.PanelWidth} PanelHeight={this.panelHeight} @@ -2556,25 +3177,29 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { AddToMap={this.AddToMap} RemFromMap={this.RemFromMap} hierarchyIndex={[]} - /> : (null) - } + /> + ) : null} </div> - { // if the document type is a presentation, then the collection stacking view has a "+ new slide" button at the bottom of the stack - <Tooltip title={<div className="dash-tooltip">{"Click on document to pin to presentaiton or make a marquee selection to pin your desired view"}</div>}> + { + // if the document type is a presentation, then the collection stacking view has a "+ new slide" button at the bottom of the stack + <Tooltip title={<div className="dash-tooltip">{'Click on document to pin to presentaiton or make a marquee selection to pin your desired view'}</div>}> <button className="add-slide-button" onPointerDown={this.startMarqueeCreateSlide}> + NEW SLIDE </button> </Tooltip> } </div> - </div>; + </div> + ); } } -ScriptingGlobals.add(function navigateToDoc(bestTarget:Doc, activeItem:Doc) { +ScriptingGlobals.add(function navigateToDoc(bestTarget: Doc, activeItem: Doc) { const srcContext = Cast(bestTarget.context, Doc, null) ?? Cast(Cast(bestTarget.annotationOn, Doc, null)?.context, Doc, null); - const openInTab = (doc: Doc, finished?: () => void) => {CollectionDockingView.AddSplit(doc, "right"); finished?.(); }; - DocumentManager.Instance.jumpToDocument(bestTarget, true, openInTab, srcContext ? [srcContext]:[], - undefined, undefined, undefined, () => PresBox.navigateToDoc(bestTarget, activeItem, true), undefined, true, NumCast(activeItem.presZoom)); -});
\ No newline at end of file + const openInTab = (doc: Doc, finished?: () => void) => { + CollectionDockingView.AddSplit(doc, 'right'); + finished?.(); + }; + DocumentManager.Instance.jumpToDocument(bestTarget, true, openInTab, srcContext ? [srcContext] : [], undefined, undefined, undefined, () => PresBox.navigateToDoc(bestTarget, activeItem, true), undefined, true, NumCast(activeItem.presZoom)); +}); diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 574193614..c5177de90 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -1,4 +1,4 @@ -import { Tooltip } from "@material-ui/core"; +import { Tooltip } from '@material-ui/core'; import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -6,13 +6,12 @@ import { DirectLinksSym, Doc, DocListCast, DocListCastAsync, Field } from '../.. import { Id } from '../../../fields/FieldSymbols'; import { StrCast } from '../../../fields/Types'; import { DocUtils } from '../../documents/Documents'; -import { DocumentType } from "../../documents/DocumentTypes"; +import { DocumentType } from '../../documents/DocumentTypes'; import { DocumentManager } from '../../util/DocumentManager'; -import { CollectionDockingView } from "../collections/CollectionDockingView"; -import { ViewBoxBaseComponent } from "../DocComponent"; +import { CollectionDockingView } from '../collections/CollectionDockingView'; +import { ViewBoxBaseComponent } from '../DocComponent'; import { FieldView, FieldViewProps } from '../nodes/FieldView'; -import "./SearchBox.scss"; - +import './SearchBox.scss'; const DAMPENING_FACTOR = 0.9; const MAX_ITERATIONS = 25; @@ -29,13 +28,15 @@ export interface SearchBoxProps extends FieldViewProps { */ @observer export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(SearchBox, fieldKey); } + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(SearchBox, fieldKey); + } public static Instance: SearchBox; private _inputRef = React.createRef<HTMLInputElement>(); - @observable _searchString = ""; - @observable _docTypeString = "all"; + @observable _searchString = ''; + @observable _docTypeString = 'all'; @observable _results: Map<Doc, string[]> = new Map<Doc, string[]>(); @observable _pageRanks: Map<Doc, number> = new Map<Doc, number>(); @observable _linkedDocsOut: Map<Doc, Set<Doc>> = new Map<Doc, Set<Doc>>(); @@ -52,7 +53,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { SearchBox.Instance = this; } - /** + /** * This method is called when the SearchBox component is first mounted. When the user opens * the search panel, the search input box is automatically selected. This allows the user to * type in the search input box immediately, without needing clicking on it first. @@ -75,7 +76,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { * This method is called when the text in the search input box is modified by the user. The * _searchString is updated to the new value of the text in the input box and submitSearch * is called to update the search results accordingly. - * + * * (Note: There is no longer a need to press enter to submit a search. Any update to the input * causes a search to be submitted automatically.) */ @@ -88,7 +89,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { * This method is called when the option in the select drop-down menu is changed. The * _docTypeString is updated to the new value of the option in the drop-down menu. This * is used to filter the results of the search to documents of a specific type. - * + * * (Note: This doesn't affect the results array, so there is no need to submit a new * search here. The results of the search on the _searchString query are simply filtered * by type directly before rendering them.) @@ -134,8 +135,8 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { docs.filter(d => d && !visited.includes(d)).forEach(d => { visited.push(d); const fieldKey = Doc.LayoutFieldKey(d); - const annos = !Field.toString(Doc.LayoutField(d) as Field).includes("CollectionView"); - const data = d[annos ? fieldKey + "-annotations" : fieldKey]; + const annos = !Field.toString(Doc.LayoutField(d) as Field).includes('CollectionView'); + const data = d[annos ? fieldKey + '-annotations' : fieldKey]; data && newarray.push(...DocListCast(data)); func(depth, d); }); @@ -156,14 +157,18 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { var depth = 0; while (docs.length > 0) { newarray = []; - await Promise.all(docs.filter(d => d).map(async d => { - const fieldKey = Doc.LayoutFieldKey(d); - const annos = !Field.toString(Doc.LayoutField(d) as Field).includes("CollectionView"); - const data = d[annos ? fieldKey + "-annotations" : fieldKey]; - const docs = await DocListCastAsync(data); - docs && newarray.push(...docs); - func(depth, d); - })); + await Promise.all( + docs + .filter(d => d) + .map(async d => { + const fieldKey = Doc.LayoutFieldKey(d); + const annos = !Field.toString(Doc.LayoutField(d) as Field).includes('CollectionView'); + const data = d[annos ? fieldKey + '-annotations' : fieldKey]; + const docs = await DocListCastAsync(data); + docs && newarray.push(...docs); + func(depth, d); + }) + ); docs = newarray; depth++; } @@ -177,11 +182,10 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { * right side of each search result. */ static formatType(type: String): String { - if (type === "pdf") { - return "PDF"; - } - else if (type === "image") { - return "Img"; + if (type === 'pdf') { + return 'PDF'; + } else if (type === 'image') { + return 'Img'; } return type.charAt(0).toUpperCase() + type.substring(1, 3); @@ -198,9 +202,40 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { @action searchCollection(query: string) { const blockedTypes = [DocumentType.PRESELEMENT, DocumentType.KVP, DocumentType.FILTER, DocumentType.SEARCH, DocumentType.SEARCHITEM, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING]; - const blockedKeys = ["x", "y", "proto", "width", "autoHeight", "acl-Override", "acl-Public", "context", "zIndex", "height", "text-scrollHeight", "text-height", "cloneFieldFilter", "isPrototype", "text-annotations", - "dragFactory-count", "text-noTemplate", "aliases", "system", "layoutKey", "baseProto", "xMargin", "yMargin", "links", "layout", "layout_keyValue", "fitWidth", "viewType", "title-custom", - "panX", "panY", "viewScale"]; + const blockedKeys = [ + 'x', + 'y', + 'proto', + 'width', + 'autoHeight', + 'acl-Override', + 'acl-Public', + 'context', + 'zIndex', + 'height', + 'text-scrollHeight', + 'text-height', + 'cloneFieldFilter', + 'isPrototype', + 'text-annotations', + 'dragFactory-count', + 'text-noTemplate', + 'aliases', + 'system', + 'layoutKey', + 'baseProto', + 'xMargin', + 'yMargin', + 'links', + 'layout', + 'layout_keyValue', + 'fitWidth', + 'viewType', + 'title-custom', + 'panX', + 'panY', + 'viewScale', + ]; const collection = CollectionDockingView.Instance; query = query.toLowerCase(); @@ -211,10 +246,15 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { const docs = DocListCast(collection.rootDoc[Doc.LayoutFieldKey(collection.rootDoc)]); const docIDs: String[] = []; SearchBox.foreachRecursiveDoc(docs, (depth: number, doc: Doc) => { - const dtype = StrCast(doc.type, "string") as DocumentType; + const dtype = StrCast(doc.type, 'string') as DocumentType; if (dtype && !blockedTypes.includes(dtype) && !docIDs.includes(doc[Id]) && depth > 0) { const hlights = new Set<string>(); - SearchBox.documentKeys(doc).forEach(key => Field.toString(doc[key] as Field).toLowerCase().includes(query) && hlights.add(key)); + SearchBox.documentKeys(doc).forEach( + key => + Field.toString(doc[key] as Field) + .toLowerCase() + .includes(query) && hlights.add(key) + ); blockedKeys.forEach(key => hlights.delete(key)); if (Array.from(hlights.keys()).length > 0) { @@ -250,18 +290,16 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { this._results.forEach((_, linkedDoc) => { this._linkedDocsIn.get(linkedDoc)?.add(doc); }); - } - else { + } else { const linkedDocSet = new Set<Doc>(); - Doc.GetProto(doc)[DirectLinksSym].forEach((link) => { + Doc.GetProto(doc)[DirectLinksSym].forEach(link => { const d1 = link?.anchor1 as Doc; const d2 = link?.anchor2 as Doc; if (doc === d1 && this._results.has(d2)) { linkedDocSet.add(d2); this._linkedDocsIn.get(d2)?.add(doc); - } - else if (doc === d2 && this._results.has(d1)) { + } else if (doc === d2 && this._results.has(d1)) { linkedDocSet.add(d1); this._linkedDocsIn.get(d1)?.add(doc); } @@ -289,8 +327,8 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { this._results.forEach((_, doc) => { let nextPageRank = pageRankFromAll; - this._linkedDocsIn.get(doc)?.forEach((linkedDoc) => { - nextPageRank += DAMPENING_FACTOR * (this._pageRanks.get(linkedDoc) ?? 0) / (this._linkedDocsOut.get(linkedDoc)?.size ?? 1); + this._linkedDocsIn.get(doc)?.forEach(linkedDoc => { + nextPageRank += (DAMPENING_FACTOR * (this._pageRanks.get(linkedDoc) ?? 0)) / (this._linkedDocsOut.get(linkedDoc)?.size ?? 1); }); nextPageRanks.set(doc, nextPageRank); @@ -328,7 +366,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { */ static documentKeys(doc: Doc) { const keys: { [key: string]: boolean } = {}; - Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => keys[key] = false)); + Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => (keys[key] = false))); return Array.from(Object.keys(keys)); } @@ -342,15 +380,13 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { const query = StrCast(this._searchString); Doc.SetSearchQuery(query); - Array.from(this._results.keys()).forEach(doc => - DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.(this._searchString, undefined, true) - ); + Array.from(this._results.keys()).forEach(doc => DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.(this._searchString, undefined, true)); this._results.clear(); if (query) { this.searchCollection(query); } - } + }; /** * This method resets the search by iterating through each result and removing all @@ -372,7 +408,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { */ selectElement = async (doc: Doc, finishFunc: () => void) => { await DocumentManager.Instance.jumpToDocument(doc, true, undefined, [], undefined, undefined, undefined, finishFunc); - } + }; /** * This method returns a JSX list of the options in the select drop-down menu, which @@ -380,9 +416,13 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { */ @computed public get selectOptions() { - const selectValues = ["all", "rtf", "image", "pdf", "web", "video", "audio", "collection"]; + const selectValues = ['all', 'rtf', 'image', 'pdf', 'web', 'video', 'audio', 'collection']; - return selectValues.map(value => <option key={value} value={value}>{SearchBox.formatType(value)}</option>); + return selectValues.map(value => ( + <option key={value} value={value}> + {SearchBox.formatType(value)} + </option> + )); } /** @@ -393,39 +433,37 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { const isLinkSearch: boolean = this.props.linkSearch; - const sortedResults = Array.from(this._results.entries()).sort((a, b) => (this._pageRanks.get(b[0]) ?? 0) - (this._pageRanks.get(a[0]) ?? 0)); // sorted by page rank + const sortedResults = Array.from(this._results.entries()).sort((a, b) => (this._pageRanks.get(b[0]) ?? 0) - (this._pageRanks.get(a[0]) ?? 0)); // sorted by page rank const resultsJSX = Array(); - sortedResults.forEach((result) => { - var className = "searchBox-results-scroll-view-result"; + sortedResults.forEach(result => { + var className = 'searchBox-results-scroll-view-result'; if (this._selectedResult === result[0]) { - className += " searchBox-results-scroll-view-result-selected"; + className += ' searchBox-results-scroll-view-result-selected'; } const formattedType = SearchBox.formatType(StrCast(result[0].type)); const title = result[0].title; - if (this._docTypeString === "all" || this._docTypeString === result[0].type) { + if (this._docTypeString === 'all' || this._docTypeString === result[0].type) { validResults++; resultsJSX.push( - <Tooltip key={result[0][Id]} placement={"right"} title={<><div className="dash-tooltip">{title}</div></>}> - <div onClick={isLinkSearch ? - () => this.makeLink(result[0]) : - e => { - this.onResultClick(result[0]); - e.stopPropagation(); - }} className={className}> - <div className="searchBox-result-title"> - {title} - </div> - <div className="searchBox-result-type"> - {formattedType} - </div> - <div className="searchBox-result-keys"> - {result[1].join(", ")} - </div> + <Tooltip key={result[0][Id]} placement={'right'} title={<div className="dash-tooltip">{title as string}</div>}> + <div + onClick={ + isLinkSearch + ? () => this.makeLink(result[0]) + : e => { + this.onResultClick(result[0]); + e.stopPropagation(); + } + } + className={className}> + <div className="searchBox-result-title">{title as string}</div> + <div className="searchBox-result-type">{formattedType}</div> + <div className="searchBox-result-keys">{result[1].join(', ')}</div> </div> </Tooltip> ); @@ -433,22 +471,31 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { }); return ( - <div style={{ pointerEvents: "all" }} className="searchBox-container"> - <div className="searchBox-bar" > - {isLinkSearch ? (null) : <select name="type" id="searchBox-type" className="searchBox-type" onChange={this.onSelectChange}> - {this.selectOptions} - </select>} - <input defaultValue={""} autoComplete="off" onChange={this.onInputChange} onKeyPress={e => e.key === "Enter" ? this.submitSearch() : null} type="text" placeholder="Search..." id="search-input" className="searchBox-input" style={{ width: isLinkSearch ? "100%" : undefined, borderRadius: isLinkSearch ? "5px" : undefined }} ref={this._inputRef} /> - </div > + <div style={{ pointerEvents: 'all' }} className="searchBox-container"> + <div className="searchBox-bar"> + {isLinkSearch ? null : ( + <select name="type" id="searchBox-type" className="searchBox-type" onChange={this.onSelectChange}> + {this.selectOptions} + </select> + )} + <input + defaultValue={''} + autoComplete="off" + onChange={this.onInputChange} + onKeyPress={e => (e.key === 'Enter' ? this.submitSearch() : null)} + type="text" + placeholder="Search..." + id="search-input" + className="searchBox-input" + style={{ width: isLinkSearch ? '100%' : undefined, borderRadius: isLinkSearch ? '5px' : undefined }} + ref={this._inputRef} + /> + </div> <div className="searchBox-results-container"> - <div className="searchBox-results-count"> - {`${validResults}` + " result" + (validResults === 1 ? "" : "s")} - </div> - <div className="searchBox-results-scroll-view"> - {resultsJSX} - </div> + <div className="searchBox-results-count">{`${validResults}` + ' result' + (validResults === 1 ? '' : 's')}</div> + <div className="searchBox-results-scroll-view">{resultsJSX}</div> </div> - </div > + </div> ); } -}
\ No newline at end of file +} diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx index bf06faeb9..e1360553a 100644 --- a/src/mobile/MobileInterface.tsx +++ b/src/mobile/MobileInterface.tsx @@ -1,52 +1,236 @@ - import { library } from '@fortawesome/fontawesome-svg-core'; import { - faTasks, faReply, faQuoteLeft, faHandPointLeft, faFolderOpen, faAngleDoubleLeft, faExternalLinkSquareAlt, faMobile, faThLarge, faWindowClose, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus, - faTerminal, faToggleOn, faFile as fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard, - faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt, - faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter, - faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, - faThumbtack, faTree, faTv, faBook, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faHome, faLongArrowAltLeft, faBars, faTh, faChevronLeft, - faAlignRight, faAlignLeft + faTasks, + faReply, + faQuoteLeft, + faHandPointLeft, + faFolderOpen, + faAngleDoubleLeft, + faExternalLinkSquareAlt, + faMobile, + faThLarge, + faWindowClose, + faEdit, + faTrashAlt, + faPalette, + faAngleRight, + faBell, + faTrash, + faCamera, + faExpand, + faCaretDown, + faCaretLeft, + faCaretRight, + faCaretSquareDown, + faCaretSquareRight, + faArrowsAltH, + faPlus, + faMinus, + faTerminal, + faToggleOn, + faFile as fileSolid, + faExternalLinkAlt, + faLocationArrow, + faSearch, + faFileDownload, + faStop, + faCalculator, + faWindowMaximize, + faAddressCard, + faQuestionCircle, + faArrowLeft, + faArrowRight, + faArrowDown, + faArrowUp, + faBolt, + faBullseye, + faCaretUp, + faCat, + faCheck, + faChevronRight, + faClipboard, + faClone, + faCloudUploadAlt, + faCommentAlt, + faCompressArrowsAlt, + faCut, + faEllipsisV, + faEraser, + faExclamation, + faFileAlt, + faFileAudio, + faFilePdf, + faFilm, + faFilter, + faFont, + faGlobeAsia, + faHighlighter, + faLongArrowAltRight, + faMicrophone, + faMousePointer, + faMusic, + faObjectGroup, + faPause, + faPen, + faPenNib, + faPhone, + faPlay, + faPortrait, + faRedoAlt, + faStamp, + faStickyNote, + faThumbtack, + faTree, + faTv, + faBook, + faUndoAlt, + faVideo, + faAsterisk, + faBrain, + faImage, + faPaintBrush, + faTimes, + faEye, + faHome, + faLongArrowAltLeft, + faBars, + faTh, + faChevronLeft, + faAlignRight, + faAlignLeft, } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; -import * as React from "react"; +import * as React from 'react'; import { Docs, DocumentOptions, DocUtils } from '../client/documents/Documents'; -import { DocumentType } from "../client/documents/DocumentTypes"; +import { DocumentType } from '../client/documents/DocumentTypes'; import { CurrentUserUtils } from '../client/util/CurrentUserUtils'; import { ScriptingGlobals } from '../client/util/ScriptingGlobals'; import { SettingsManager, ColorScheme } from '../client/util/SettingsManager'; import { Transform } from '../client/util/Transform'; -import { UndoManager } from "../client/util/UndoManager"; +import { UndoManager } from '../client/util/UndoManager'; import { TabDocView } from '../client/views/collections/TabDocView'; -import { CollectionViewType } from "../client/views/collections/CollectionView"; -import { GestureOverlay } from "../client/views/GestureOverlay"; -import { AudioBox } from "../client/views/nodes/AudioBox"; +import { CollectionViewType } from '../client/views/collections/CollectionView'; +import { GestureOverlay } from '../client/views/GestureOverlay'; +import { AudioBox } from '../client/views/nodes/AudioBox'; import { DocumentView } from '../client/views/nodes/DocumentView'; -import { RichTextMenu } from "../client/views/nodes/formattedText/RichTextMenu"; -import { RadialMenu } from "../client/views/nodes/RadialMenu"; +import { RichTextMenu } from '../client/views/nodes/formattedText/RichTextMenu'; +import { RadialMenu } from '../client/views/nodes/RadialMenu'; import { Doc, DocListCast } from '../fields/Doc'; import { InkTool } from '../fields/InkField'; -import { List } from "../fields/List"; -import { ScriptField } from "../fields/ScriptField"; -import { Cast, FieldValue } from '../fields/Types'; +import { List } from '../fields/List'; +import { ScriptField } from '../fields/ScriptField'; +import { Cast, FieldValue, StrCast } from '../fields/Types'; import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero } from '../Utils'; -import { AudioUpload } from "./AudioUpload"; -import { Uploader } from "./ImageUpload"; -import "./AudioUpload.scss"; -import "./ImageUpload.scss"; -import "./MobileInterface.scss"; - -library.add(...[faTasks, faReply, faQuoteLeft, faHandPointLeft, faFolderOpen, faAngleDoubleLeft, faExternalLinkSquareAlt, faMobile, faThLarge, faWindowClose, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus, - faTerminal, faToggleOn, fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard, - faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt, - faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter, - faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, - faThumbtack, faTree, faTv, faUndoAlt, faBook, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faHome, faLongArrowAltLeft, faBars, faTh, faChevronLeft, - faAlignLeft, faAlignRight].map(m => m as any)); - +import { AudioUpload } from './AudioUpload'; +import { Uploader } from './ImageUpload'; +import './AudioUpload.scss'; +import './ImageUpload.scss'; +import './MobileInterface.scss'; + +library.add( + ...[ + faTasks, + faReply, + faQuoteLeft, + faHandPointLeft, + faFolderOpen, + faAngleDoubleLeft, + faExternalLinkSquareAlt, + faMobile, + faThLarge, + faWindowClose, + faEdit, + faTrashAlt, + faPalette, + faAngleRight, + faBell, + faTrash, + faCamera, + faExpand, + faCaretDown, + faCaretLeft, + faCaretRight, + faCaretSquareDown, + faCaretSquareRight, + faArrowsAltH, + faPlus, + faMinus, + faTerminal, + faToggleOn, + fileSolid, + faExternalLinkAlt, + faLocationArrow, + faSearch, + faFileDownload, + faStop, + faCalculator, + faWindowMaximize, + faAddressCard, + faQuestionCircle, + faArrowLeft, + faArrowRight, + faArrowDown, + faArrowUp, + faBolt, + faBullseye, + faCaretUp, + faCat, + faCheck, + faChevronRight, + faClipboard, + faClone, + faCloudUploadAlt, + faCommentAlt, + faCompressArrowsAlt, + faCut, + faEllipsisV, + faEraser, + faExclamation, + faFileAlt, + faFileAudio, + faFilePdf, + faFilm, + faFilter, + faFont, + faGlobeAsia, + faHighlighter, + faLongArrowAltRight, + faMicrophone, + faMousePointer, + faMusic, + faObjectGroup, + faPause, + faPen, + faPenNib, + faPhone, + faPlay, + faPortrait, + faRedoAlt, + faStamp, + faStickyNote, + faThumbtack, + faTree, + faTv, + faUndoAlt, + faBook, + faVideo, + faAsterisk, + faBrain, + faImage, + faPaintBrush, + faTimes, + faEye, + faHome, + faLongArrowAltLeft, + faBars, + faTh, + faChevronLeft, + faAlignLeft, + faAlignRight, + ].map(m => m as any) +); @observer export class MobileInterface extends React.Component { @@ -64,36 +248,38 @@ export class MobileInterface extends React.Component { @observable private _homeDoc: Doc = this._mainDoc; // home menu as a document @observable private _parents: Array<Doc> = []; // array of parent docs (for pathbar) - @computed private get mainContainer() { return Doc.UserDoc() ? FieldValue(Cast(Doc.UserDoc().activeMobile, Doc)) : CurrentUserUtils.GuestMobile; } + @computed private get mainContainer() { + return Doc.UserDoc() ? FieldValue(Cast(Doc.UserDoc().activeMobile, Doc)) : CurrentUserUtils.GuestMobile; + } constructor(props: Readonly<{}>) { super(props); - this._library = CurrentUserUtils.setupDashboards(Doc.UserDoc(), "myDashboards"); // to access documents in Dash Web + this._library = CurrentUserUtils.setupDashboards(Doc.UserDoc(), 'myDashboards'); // to access documents in Dash Web MobileInterface.Instance = this; } @action componentDidMount = () => { // if the home menu is in list view -> adjust the menu toggle appropriately - this._menuListView = this._homeDoc._viewType === "stacking" ? true : false; + this._menuListView = this._homeDoc._viewType === 'stacking' ? true : false; CurrentUserUtils.ActiveTool = InkTool.None; // ink should intially be set to none Doc.UserDoc().activeMobile = this._homeDoc; // active mobile set to home AudioBox.Enabled = true; // remove double click to avoid mobile zoom in - document.removeEventListener("dblclick", this.onReactDoubleClick); - document.addEventListener("dblclick", this.onReactDoubleClick); - } + document.removeEventListener('dblclick', this.onReactDoubleClick); + document.addEventListener('dblclick', this.onReactDoubleClick); + }; @action componentWillUnmount = () => { document.removeEventListener('dblclick', this.onReactDoubleClick); - } + }; // Prevent zooming in when double tapping the screen onReactDoubleClick = (e: MouseEvent) => { e.stopPropagation(); - } + }; // Switch the mobile view to the given doc @action @@ -110,7 +296,7 @@ export class MobileInterface extends React.Component { // Ensures that switching to home is not registed UndoManager.undoStack.length = 0; UndoManager.redoStack.length = 0; - } + }; // For toggling the hamburger menu @action @@ -120,29 +306,29 @@ export class MobileInterface extends React.Component { if (this._ink) { this.onSwitchInking(); } - } + }; /** * Method called when 'Library' button is pressed on the home screen */ switchToLibrary = async () => { this.switchCurrentView(this._library); - runInAction(() => this._homeMenu = false); + runInAction(() => (this._homeMenu = false)); this.toggleSidebar(); - } + }; /** * Back method for navigating through items */ @action back = () => { - const header = document.getElementById("header") as HTMLElement; + const header = document.getElementById('header') as HTMLElement; const doc = Cast(this._parents.pop(), Doc) as Doc; // Parent document // Case 1: Parent document is 'dashboards' - if (doc === Cast(this._library, Doc) as Doc) { + if (doc === (Cast(this._library, Doc) as Doc)) { this.dashboards = null; this.switchCurrentView(this._library); // Case 2: Parent document is the 'home' menu (root node) - } else if (doc === Cast(this._homeDoc, Doc) as Doc) { + } else if (doc === (Cast(this._homeDoc, Doc) as Doc)) { this._homeMenu = true; this._parents = []; this.dashboards = null; @@ -155,7 +341,7 @@ export class MobileInterface extends React.Component { header.textContent = String(doc.title); } this._ink = false; // turns ink off - } + }; /** * Return 'Home", which implies returning to 'Home' menu buttons @@ -171,7 +357,7 @@ export class MobileInterface extends React.Component { if (this._sidebarActive) { this.toggleSidebar(); } - } + }; /** * Return to primary Dashboard in library (Dashboards Doc) @@ -182,7 +368,7 @@ export class MobileInterface extends React.Component { this.switchCurrentView(this._library); this._homeMenu = false; this.dashboards = null; - } + }; /** * Note: window.innerWidth and window.screen.width compute different values. @@ -190,14 +376,14 @@ export class MobileInterface extends React.Component { * display resolution which computes differently. */ returnWidth = () => window.innerWidth; //The windows width - returnHeight = () => (window.innerHeight - 300); //Calculating the windows height (-300 to account for topbar) - whitebackground = () => "white"; + returnHeight = () => window.innerHeight - 300; //Calculating the windows height (-300 to account for topbar) + whitebackground = () => 'white'; /** * DocumentView for graphic display of all documents */ @computed get displayDashboards() { - return !this.mainContainer ? (null) : - <div style={{ position: "relative", top: '198px', height: `calc(100% - 350px)`, width: "100%", left: "0%" }}> + return !this.mainContainer ? null : ( + <div style={{ position: 'relative', top: '198px', height: `calc(100% - 350px)`, width: '100%', left: '0%' }}> <DocumentView Document={this.mainContainer} DataDoc={undefined} @@ -223,7 +409,8 @@ export class MobileInterface extends React.Component { ContainingCollectionView={undefined} ContainingCollectionDoc={undefined} /> - </div>; + </div> + ); } /** @@ -233,20 +420,19 @@ export class MobileInterface extends React.Component { */ handleClick = async (doc: Doc) => { runInAction(() => { - if (doc.type !== "collection" && this._sidebarActive) { + if (doc.type !== 'collection' && this._sidebarActive) { this._parents.push(this._activeDoc); this.switchCurrentView(doc); this._homeMenu = false; this.toggleSidebar(); - } - else { + } else { this._parents.push(this._activeDoc); this.switchCurrentView(doc); this._homeMenu = false; this.dashboards = doc; } }); - } + }; /** * Called when an item in the library is clicked and should @@ -260,30 +446,31 @@ export class MobileInterface extends React.Component { this._homeMenu = false; this.dashboards = doc; this.toggleSidebar(); - } + }; // Renders the graphical pathbar renderPathbar = () => { const docPath = [...this._parents, this._activeDoc]; - const items = docPath.map((doc: Doc, index: any) => + const items = docPath.map((doc: Doc, index: any) => ( <div className="pathbarItem" key={index}> - {index === 0 ? (null) : <FontAwesomeIcon key="icon" className="pathIcon" icon="angle-right" size="lg" />} - <div className="pathbarText" - style={{ backgroundColor: this._homeMenu || doc === this._activeDoc ? "rgb(119,17,37)" : undefined }} - onClick={() => this.handlePathClick(doc, index)}>{doc.title} + {index === 0 ? null : <FontAwesomeIcon key="icon" className="pathIcon" icon="angle-right" size="lg" />} + <div className="pathbarText" style={{ backgroundColor: this._homeMenu || doc === this._activeDoc ? 'rgb(119,17,37)' : undefined }} onClick={() => this.handlePathClick(doc, index)}> + {StrCast(doc.title)} </div> - </div>); - return (<div className="pathbar"> - <div className="scrollmenu"> - {items} </div> - {!this._parents.length ? (null) : - <div className="back" > - <FontAwesomeIcon onClick={this.back} icon={"chevron-left"} color="white" size={"2x"} /> - </div>} - <div className="hidePath" /> - </div>); - } + )); + return ( + <div className="pathbar"> + <div className="scrollmenu">{items}</div> + {!this._parents.length ? null : ( + <div className="back"> + <FontAwesomeIcon onClick={this.back} icon={'chevron-left'} color="white" size={'2x'} /> + </div> + )} + <div className="hidePath" /> + </div> + ); + }; // Handles when user clicks on a document in the pathbar @action @@ -300,7 +487,7 @@ export class MobileInterface extends React.Component { this.switchCurrentView(doc); this._parents.length = index; } - } + }; // Renders the contents of the menu and sidebar @computed get renderDefaultContent() { @@ -309,7 +496,9 @@ export class MobileInterface extends React.Component { <div> <div className="navbar"> <FontAwesomeIcon className="home" icon="home" onClick={this.returnHome} /> - <div className="header" id="header">{this._homeDoc.title}</div> + <div className="header" id="header"> + {StrCast(this._homeDoc.title)} + </div> <div className="cover" id="cover" onClick={e => e.stopPropagation()}></div> <div className="toggle-btn" id="menuButton" onClick={this.toggleSidebar}> <span></span> @@ -328,14 +517,17 @@ export class MobileInterface extends React.Component { } // returns a list of navbar buttons as 'buttons' const buttons = DocListCast(dashboards.data).map((doc: Doc, index: any) => { - if (doc.type !== "ink") { + if (doc.type !== 'ink') { return ( - <div - className="item" - key={index}> - <div className="item-title" onClick={() => this.handleClick(doc)}> {doc.title} </div> - <div className="item-type" onClick={() => this.handleClick(doc)}>{doc.type}</div> - <FontAwesomeIcon onClick={() => this.handleClick(doc)} className="right" icon="angle-right" size="lg" style={{ display: `${doc.type === "collection" ? "block" : "none"}` }} /> + <div className="item" key={index}> + <div className="item-title" onClick={() => this.handleClick(doc)}> + {' '} + {doc.title as string}{' '} + </div> + <div className="item-type" onClick={() => this.handleClick(doc)}> + {doc.type as string} + </div> + <FontAwesomeIcon onClick={() => this.handleClick(doc)} className="right" icon="angle-right" size="lg" style={{ display: `${doc.type === 'collection' ? 'block' : 'none'}` }} /> <FontAwesomeIcon className="open" onClick={() => this.openFromSidebar(doc)} icon="external-link-alt" size="lg" /> </div> ); @@ -346,43 +538,39 @@ export class MobileInterface extends React.Component { <div> <div className="navbar"> <FontAwesomeIcon className="home" icon="home" onClick={this.returnHome} /> - <div className="header" id="header">{this._sidebarActive ? "library" : this._activeDoc.title}</div> - <div className={`toggle-btn ${this._sidebarActive ? "active" : ""}`} onClick={this.toggleSidebar}> + <div className="header" id="header"> + {this._sidebarActive ? 'library' : (this._activeDoc.title as string)} + </div> + <div className={`toggle-btn ${this._sidebarActive ? 'active' : ''}`} onClick={this.toggleSidebar}> <span></span> <span></span> <span></span> </div> - <div className={`background ${this._sidebarActive ? "active" : ""}`} onClick={this.toggleSidebar}></div> + <div className={`background ${this._sidebarActive ? 'active' : ''}`} onClick={this.toggleSidebar}></div> </div> {this.renderPathbar()} - <div className={`sidebar ${this._sidebarActive ? "active" : ""}`}> + <div className={`sidebar ${this._sidebarActive ? 'active' : ''}`}> <div className="sidebarButtons"> - {this.dashboards ? + {this.dashboards ? ( <> {buttons} - <div - className="item" key="home" - onClick={this.returnMain} - style={{ opacity: 0.7 }}> + <div className="item" key="home" onClick={this.returnMain} style={{ opacity: 0.7 }}> <FontAwesomeIcon className="right" icon="angle-double-left" size="lg" /> <div className="item-type">Return to dashboards</div> </div> - </> : + </> + ) : ( <> {buttons} - <div - className="item" - style={{ opacity: 0.7 }} - onClick={() => this.createNewDashboard()}> + <div className="item" style={{ opacity: 0.7 }} onClick={() => this.createNewDashboard()}> <FontAwesomeIcon className="right" icon="plus" size="lg" /> <div className="item-type">Create New Dashboard</div> </div> </> - } + )} </div> </div> - <div className={`blanket ${this._sidebarActive ? "active" : ""}`} onClick={this.toggleSidebar}> - </div> + <div className={`blanket ${this._sidebarActive ? 'active' : ''}`} onClick={this.toggleSidebar}></div> </div> ); } @@ -397,26 +585,26 @@ export class MobileInterface extends React.Component { const freeformOptions: DocumentOptions = { x: 0, y: 400, - title: "Collection " + dashboardCount, + title: 'Collection ' + dashboardCount, }; const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions); - const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: `Dashboard ${dashboardCount}` }, id, "row"); + const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: `Dashboard ${dashboardCount}` }, id, 'row'); const toggleTheme = ScriptField.MakeScript(`self.colorScheme = self.colorScheme ? undefined: ${ColorScheme.Dark}}`); const toggleComic = ScriptField.MakeScript(`toggleComicMode()`); const cloneDashboard = ScriptField.MakeScript(`cloneDashboard()`); dashboardDoc.contextMenuScripts = new List<ScriptField>([toggleTheme!, toggleComic!, cloneDashboard!]); - dashboardDoc.contextMenuLabels = new List<string>(["Toggle Theme Colors", "Toggle Comic Mode", "New Dashboard Layout"]); + dashboardDoc.contextMenuLabels = new List<string>(['Toggle Theme Colors', 'Toggle Comic Mode', 'New Dashboard Layout']); - Doc.AddDocToList(scens, "data", dashboardDoc); - } + Doc.AddDocToList(scens, 'data', dashboardDoc); + }; // Button for switching between pen and ink mode @action onSwitchInking = () => { - const button = document.getElementById("inkButton") as HTMLElement; - button.style.backgroundColor = this._ink ? "white" : "black"; - button.style.color = this._ink ? "black" : "white"; + const button = document.getElementById('inkButton') as HTMLElement; + button.style.backgroundColor = this._ink ? 'white' : 'black'; + button.style.color = this._ink ? 'black' : 'white'; if (!this._ink) { CurrentUserUtils.ActiveTool = InkTool.Pen; @@ -425,23 +613,20 @@ export class MobileInterface extends React.Component { CurrentUserUtils.ActiveTool = InkTool.None; this._ink = false; } - } + }; // The static ink menu that appears at the top @computed get inkMenu() { - return this._activeDoc._viewType !== CollectionViewType.Docking || !this._ink ? (null) : - <div className="colorSelector"> - {/* <CollectionFreeFormViewChrome /> */} - </div>; + return this._activeDoc._viewType !== CollectionViewType.Docking || !this._ink ? null : <div className="colorSelector">{/* <CollectionFreeFormViewChrome /> */}</div>; } // DocButton that uses UndoManager and handles the opacity change if CanUndo is true @computed get undo() { - if (this.mainContainer && this._activeDoc.type === "collection" && this._activeDoc !== this._homeDoc && - this._activeDoc !== Doc.SharingDoc() && this._activeDoc.title !== "WORKSPACES") { + if (this.mainContainer && this._activeDoc.type === 'collection' && this._activeDoc !== this._homeDoc && this._activeDoc !== Doc.SharingDoc() && this._activeDoc.title !== 'WORKSPACES') { return ( - <div className="docButton" - style={{ backgroundColor: "black", color: "white", fontSize: "60", opacity: UndoManager.CanUndo() ? "1" : "0.4", }} + <div + className="docButton" + style={{ backgroundColor: 'black', color: 'white', fontSize: '60', opacity: UndoManager.CanUndo() ? '1' : '0.4' }} id="undoButton" title="undo" onClick={(e: React.MouseEvent) => { @@ -449,17 +634,18 @@ export class MobileInterface extends React.Component { e.stopPropagation(); }}> <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="undo-alt" /> - </div>); - } else return (null); + </div> + ); + } else return null; } // DocButton that uses UndoManager and handles the opacity change if CanRedo is true @computed get redo() { - if (this.mainContainer && this._activeDoc.type === "collection" && this._activeDoc !== this._homeDoc && - this._activeDoc !== Doc.SharingDoc() && this._activeDoc.title !== "WORKSPACES") { + if (this.mainContainer && this._activeDoc.type === 'collection' && this._activeDoc !== this._homeDoc && this._activeDoc !== Doc.SharingDoc() && this._activeDoc.title !== 'WORKSPACES') { return ( - <div className="docButton" - style={{ backgroundColor: "black", color: "white", fontSize: "60", opacity: UndoManager.CanRedo() ? "1" : "0.4", }} + <div + className="docButton" + style={{ backgroundColor: 'black', color: 'white', fontSize: '60', opacity: UndoManager.CanRedo() ? '1' : '0.4' }} id="undoButton" title="redo" onClick={(e: React.MouseEvent) => { @@ -467,78 +653,81 @@ export class MobileInterface extends React.Component { e.stopPropagation(); }}> <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="redo-alt" /> - </div>); - } else return (null); + </div> + ); + } else return null; } // DocButton for switching into ink mode @computed get drawInk() { - return !this.mainContainer || this._activeDoc._viewType !== CollectionViewType.Docking ? (null) : - <div className="docButton" - id="inkButton" - title={Doc.isDocPinned(this._activeDoc) ? "Pen on" : "Pen off"} - onClick={this.onSwitchInking}> + return !this.mainContainer || this._activeDoc._viewType !== CollectionViewType.Docking ? null : ( + <div className="docButton" id="inkButton" title={Doc.isDocPinned(this._activeDoc) ? 'Pen on' : 'Pen off'} onClick={this.onSwitchInking}> <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="pen-nib" /> - </div>; + </div> + ); } // DocButton: Button that appears on the bottom of the screen to initiate image upload @computed get uploadImageButton() { - if (this._activeDoc.type === DocumentType.COL && this._activeDoc !== this._homeDoc && this._activeDoc._viewType !== CollectionViewType.Docking && this._activeDoc.title !== "WORKSPACES") { - return <div className="docButton" - id="imageButton" - title={Doc.isDocPinned(this._activeDoc) ? "Pen on" : "Pen off"} - onClick={this.toggleUpload}> - <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="upload" /> - </div>; - } else return (null); + if (this._activeDoc.type === DocumentType.COL && this._activeDoc !== this._homeDoc && this._activeDoc._viewType !== CollectionViewType.Docking && this._activeDoc.title !== 'WORKSPACES') { + return ( + <div className="docButton" id="imageButton" title={Doc.isDocPinned(this._activeDoc) ? 'Pen on' : 'Pen off'} onClick={this.toggleUpload}> + <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="upload" /> + </div> + ); + } else return null; } // DocButton to download images on the mobile @computed get downloadDocument() { - if (this._activeDoc.type === "image" || this._activeDoc.type === "pdf" || this._activeDoc.type === "video") { - return <div className="docButton" - title={"Download Image"} - style={{ backgroundColor: "white", color: "black" }} - onClick={e => window.open(this._activeDoc["data-path"]?.toString())}> {/* daa-path holds the url */} - <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="download" /> - </div>; - } else return (null); + if (this._activeDoc.type === 'image' || this._activeDoc.type === 'pdf' || this._activeDoc.type === 'video') { + return ( + <div className="docButton" title={'Download Image'} style={{ backgroundColor: 'white', color: 'black' }} onClick={e => window.open(this._activeDoc['data-path']?.toString())}> + {' '} + {/* daa-path holds the url */} + <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="download" /> + </div> + ); + } else return null; } // DocButton for pinning images to presentation @computed get pinToPresentation() { // Only making button available if it is an image - if (!(this._activeDoc.type === "collection" || this._activeDoc.type === "presentation")) { + if (!(this._activeDoc.type === 'collection' || this._activeDoc.type === 'presentation')) { const isPinned = this._activeDoc && Doc.isDocPinned(this._activeDoc); - return <div className="docButton" - title={Doc.isDocPinned(this._activeDoc) ? "Unpin from presentation" : "Pin to presentation"} - style={{ backgroundColor: isPinned ? "black" : "white", color: isPinned ? "white" : "black" }} - onClick={e => TabDocView.PinDoc(this._activeDoc)}> - <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin" /> - </div>; - } else return (null); + return ( + <div + className="docButton" + title={Doc.isDocPinned(this._activeDoc) ? 'Unpin from presentation' : 'Pin to presentation'} + style={{ backgroundColor: isPinned ? 'black' : 'white', color: isPinned ? 'white' : 'black' }} + onClick={e => TabDocView.PinDoc(this._activeDoc)}> + <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin" /> + </div> + ); + } else return null; } // Buttons for switching the menu between large and small icons @computed get switchMenuView() { - return this._activeDoc.title !== this._homeDoc.title ? (null) : + return this._activeDoc.title !== this._homeDoc.title ? null : ( <div className="homeSwitch"> - <div className={`list ${!this._menuListView ? "active" : ""}`} onClick={this.changeToIconView}> + <div className={`list ${!this._menuListView ? 'active' : ''}`} onClick={this.changeToIconView}> <FontAwesomeIcon size="sm" icon="th-large" /> </div> - <div className={`list ${this._menuListView ? "active" : ""}`} onClick={this.changeToListView}> + <div className={`list ${this._menuListView ? 'active' : ''}`} onClick={this.changeToListView}> <FontAwesomeIcon size="sm" icon="bars" /> </div> - </div>; + </div> + ); } // Logic for switching the menu into the icons @action changeToIconView = () => { - if (this._homeDoc._viewType = "stacking") { + if ((this._homeDoc._viewType = 'stacking')) { this._menuListView = false; - this._homeDoc._viewType = "masonry"; + this._homeDoc._viewType = 'masonry'; this._homeDoc.columnWidth = 300; this._homeDoc._columnWidth = 300; const menuButtons = DocListCast(this._homeDoc.data); @@ -551,13 +740,13 @@ export class MobileInterface extends React.Component { doc._nativeWidth = 400; }); } - } + }; // Logic for switching the menu into the stacking view @action changeToListView = () => { - if (this._homeDoc._viewType = "masonry") { - this._homeDoc._viewType = "stacking"; + if ((this._homeDoc._viewType = 'masonry')) { + this._homeDoc._viewType = 'stacking'; this._menuListView = true; const menuButtons = DocListCast(this._homeDoc.data); menuButtons.map(doc => { @@ -568,7 +757,7 @@ export class MobileInterface extends React.Component { doc._nativeWidth = 900; }); } - } + }; // For setting up the presentation document for the home menu @action @@ -579,49 +768,48 @@ export class MobileInterface extends React.Component { this.switchCurrentView(presentation); this._homeMenu = false; } - } + }; // For toggling image upload pop up @action - toggleUpload = () => this._imageUploadActive = !this._imageUploadActive + toggleUpload = () => (this._imageUploadActive = !this._imageUploadActive); // For toggling audio record and dictate pop up @action - toggleAudio = () => this._audioUploadActive = !this._audioUploadActive + toggleAudio = () => (this._audioUploadActive = !this._audioUploadActive); // Button for toggling the upload pop up in a collection @action toggleUploadInCollection = () => { - const button = document.getElementById("imageButton") as HTMLElement; - button.style.backgroundColor = this._imageUploadActive ? "white" : "black"; - button.style.color = this._imageUploadActive ? "black" : "white"; + const button = document.getElementById('imageButton') as HTMLElement; + button.style.backgroundColor = this._imageUploadActive ? 'white' : 'black'; + button.style.color = this._imageUploadActive ? 'black' : 'white'; this._imageUploadActive = !this._imageUploadActive; - } + }; // For closing the image upload pop up @action closeUpload = () => { this._imageUploadActive = false; - } + }; // Returns the image upload pop up @computed get uploadImage() { - const doc = !this._homeMenu ? this._activeDoc : Cast(Doc.SharingDoc(), Doc) as Doc; + const doc = !this._homeMenu ? this._activeDoc : (Cast(Doc.SharingDoc(), Doc) as Doc); return <Uploader Document={doc} />; } - // Radial menu can only be used if it is a colleciton and it is not a homeDoc + // Radial menu can only be used if it is a colleciton and it is not a homeDoc // (and cannot be used on Dashboard to avoid pin to presentation opening on right) @computed get displayRadialMenu() { - return this._activeDoc.type === "collection" && this._activeDoc !== this._homeDoc && - this._activeDoc._viewType !== CollectionViewType.Docking ? <RadialMenu /> : (null); + return this._activeDoc.type === 'collection' && this._activeDoc !== this._homeDoc && this._activeDoc._viewType !== CollectionViewType.Docking ? <RadialMenu /> : null; } onDragOver = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); - } + }; /** * MENU BUTTON @@ -633,22 +821,22 @@ export class MobileInterface extends React.Component { const mobileUpload = Cast(Doc.SharingDoc(), Doc) as Doc; this.switchCurrentView(mobileUpload); this._homeMenu = false; - } + }; render() { return ( <div className="mobileInterface-container" onDragOver={this.onDragOver}> <SettingsManager /> - <div className={`image-upload ${this._imageUploadActive ? "active" : ""}`}> - {this.uploadImage} - </div> - <div className={`audio-upload ${this._audioUploadActive ? "active" : ""}`}> + <div className={`image-upload ${this._imageUploadActive ? 'active' : ''}`}>{this.uploadImage}</div> + <div className={`audio-upload ${this._audioUploadActive ? 'active' : ''}`}> <AudioUpload /> </div> {this.switchMenuView} {this.inkMenu} <GestureOverlay> - <div style={{ display: "none" }}><RichTextMenu key="rich" /></div> + <div style={{ display: 'none' }}> + <RichTextMenu key="rich" /> + </div> <div className="docButtonContainer"> {this.pinToPresentation} {this.downloadDocument} @@ -666,21 +854,31 @@ export class MobileInterface extends React.Component { } } - //Global functions for mobile menu -ScriptingGlobals.add(function switchToMobileLibrary() { return MobileInterface.Instance.switchToLibrary(); }, - "opens the library to navigate through dashboards on Dash Mobile"); -ScriptingGlobals.add(function openMobileUploads() { return MobileInterface.Instance.toggleUpload(); }, - "opens the upload files menu for Dash Mobile"); -ScriptingGlobals.add(function switchToMobileUploadCollection() { return MobileInterface.Instance.switchToMobileUploads(); }, - "opens the mobile uploads collection on Dash Mobile"); -ScriptingGlobals.add(function openMobileAudio() { return MobileInterface.Instance.toggleAudio(); }, - "opens the record and dictate menu on Dash Mobile"); -ScriptingGlobals.add(function switchToMobilePresentation() { return MobileInterface.Instance.setupDefaultPresentation(); }, - "opens the presentation on Dash Mobile"); -ScriptingGlobals.add(function openMobileSettings() { return SettingsManager.Instance.open(); }, - "opens settings on Dash Mobile"); +ScriptingGlobals.add(function switchToMobileLibrary() { + return MobileInterface.Instance.switchToLibrary(); +}, 'opens the library to navigate through dashboards on Dash Mobile'); +ScriptingGlobals.add(function openMobileUploads() { + return MobileInterface.Instance.toggleUpload(); +}, 'opens the upload files menu for Dash Mobile'); +ScriptingGlobals.add(function switchToMobileUploadCollection() { + return MobileInterface.Instance.switchToMobileUploads(); +}, 'opens the mobile uploads collection on Dash Mobile'); +ScriptingGlobals.add(function openMobileAudio() { + return MobileInterface.Instance.toggleAudio(); +}, 'opens the record and dictate menu on Dash Mobile'); +ScriptingGlobals.add(function switchToMobilePresentation() { + return MobileInterface.Instance.setupDefaultPresentation(); +}, 'opens the presentation on Dash Mobile'); +ScriptingGlobals.add(function openMobileSettings() { + return SettingsManager.Instance.open(); +}, 'opens settings on Dash Mobile'); // Other global functions for mobile -ScriptingGlobals.add(function switchMobileView(doc: Doc, renderView?: () => JSX.Element, onSwitch?: () => void) { return MobileInterface.Instance.switchCurrentView(doc, renderView, onSwitch); }, - "changes the active document displayed on the Dash Mobile", "(doc: any)");
\ No newline at end of file +ScriptingGlobals.add( + function switchMobileView(doc: Doc, renderView?: () => JSX.Element, onSwitch?: () => void) { + return MobileInterface.Instance.switchCurrentView(doc, renderView, onSwitch); + }, + 'changes the active document displayed on the Dash Mobile', + '(doc: any)' +); diff --git a/src/server/authentication/AuthenticationManager.ts b/src/server/authentication/AuthenticationManager.ts index b736f0d35..3622be4c5 100644 --- a/src/server/authentication/AuthenticationManager.ts +++ b/src/server/authentication/AuthenticationManager.ts @@ -1,14 +1,14 @@ -import { default as User, DashUserModel } from "./DashUserModel"; -import { Request, Response, NextFunction } from "express"; -import * as passport from "passport"; -import { IVerifyOptions } from "passport-local"; -import "./Passport"; -import flash = require("express-flash"); +import { default as User, DashUserModel } from './DashUserModel'; +import { Request, Response, NextFunction } from 'express'; +import * as passport from 'passport'; +import { IVerifyOptions } from 'passport-local'; +import './Passport'; +import flash = require('express-flash'); import * as async from 'async'; import * as nodemailer from 'nodemailer'; -import c = require("crypto"); -import { Utils } from "../../Utils"; -import { MailOptions } from "nodemailer/lib/stream-transport"; +import c = require('crypto'); +import { emptyFunction, Utils } from '../../Utils'; +import { MailOptions } from 'nodemailer/lib/stream-transport'; /** * GET /signup @@ -17,10 +17,10 @@ import { MailOptions } from "nodemailer/lib/stream-transport"; */ export let getSignup = (req: Request, res: Response) => { if (req.user) { - return res.redirect("/home"); + return res.redirect('/home'); } - res.render("signup.pug", { - title: "Sign Up", + res.render('signup.pug', { + title: 'Sign Up', user: req.user, }); }; @@ -30,15 +30,15 @@ export let getSignup = (req: Request, res: Response) => { * Create a new local account. */ export let postSignup = (req: Request, res: Response, next: NextFunction) => { - req.assert("email", "Email is not valid").isEmail(); - req.assert("password", "Password must be at least 4 characters long").len({ min: 4 }); - req.assert("confirmPassword", "Passwords do not match").equals(req.body.password); - req.sanitize("email").normalizeEmail({ gmail_remove_dots: false }); + req.assert('email', 'Email is not valid').isEmail(); + req.assert('password', 'Password must be at least 4 characters long').len({ min: 4 }); + req.assert('confirmPassword', 'Passwords do not match').equals(req.body.password); + req.sanitize('email').normalizeEmail({ gmail_remove_dots: false }); const errors = req.validationErrors(); if (errors) { - return res.redirect("/signup"); + return res.redirect('/signup'); } const email = req.body.email as String; @@ -50,25 +50,30 @@ export let postSignup = (req: Request, res: Response, next: NextFunction) => { userDocumentId: Utils.GenerateGuid(), sharingDocumentId: Utils.GenerateGuid(), linkDatabaseId: Utils.GenerateGuid(), - cacheDocumentIds: "" + cacheDocumentIds: '', } as Partial<DashUserModel>; const user = new User(model); User.findOne({ email }, (err: any, existingUser: any) => { - if (err) { return next(err); } + if (err) { + return next(err); + } if (existingUser) { - return res.redirect("/login"); + return res.redirect('/login'); } user.save((err: any) => { - if (err) { return next(err); } - req.logIn(user, (err) => { - if (err) { return next(err); } + if (err) { + return next(err); + } + req.logIn(user, err => { + if (err) { + return next(err); + } tryRedirectToTarget(req, res); }); }); }); - }; const tryRedirectToTarget = (req: Request, res: Response) => { @@ -76,11 +81,10 @@ const tryRedirectToTarget = (req: Request, res: Response) => { if (req.session && target) { res.redirect(target); } else { - res.redirect("/home"); + res.redirect('/home'); } }; - /** * GET /login * Login page. @@ -88,11 +92,11 @@ const tryRedirectToTarget = (req: Request, res: Response) => { export let getLogin = (req: Request, res: Response) => { if (req.user) { //req.session.target = undefined; - return res.redirect("/home"); + return res.redirect('/home'); } - res.render("login.pug", { - title: "Log In", - user: req.user + res.render('login.pug', { + title: 'Log In', + user: req.user, }); }; @@ -102,24 +106,30 @@ export let getLogin = (req: Request, res: Response) => { * On failure, redirect to signup page */ export let postLogin = (req: Request, res: Response, next: NextFunction) => { - req.assert("email", "Email is not valid").isEmail(); - req.assert("password", "Password cannot be blank").notEmpty(); - req.sanitize("email").normalizeEmail({ gmail_remove_dots: false }); + req.assert('email', 'Email is not valid').isEmail(); + req.assert('password', 'Password cannot be blank').notEmpty(); + req.sanitize('email').normalizeEmail({ gmail_remove_dots: false }); const errors = req.validationErrors(); if (errors) { - req.flash("errors", "Unable to login at this time. Please try again."); - return res.redirect("/signup"); + req.flash('errors', 'Unable to login at this time. Please try again.'); + return res.redirect('/signup'); } - passport.authenticate("local", (err: Error, user: DashUserModel, _info: IVerifyOptions) => { - if (err) { next(err); return; } + passport.authenticate('local', (err: Error, user: DashUserModel, _info: IVerifyOptions) => { + if (err) { + next(err); + return; + } if (!user) { - return res.redirect("/signup"); + return res.redirect('/signup'); } - req.logIn(user, (err) => { - if (err) { next(err); return; } + req.logIn(user, err => { + if (err) { + next(err); + return; + } tryRedirectToTarget(req, res); }); })(req, res, next); @@ -131,140 +141,154 @@ export let postLogin = (req: Request, res: Response, next: NextFunction) => { * and destroys the user's current session. */ export let getLogout = (req: Request, res: Response) => { - req.logout(); + req.logout(emptyFunction); const sess = req.session; if (sess) { - sess.destroy((err) => { if (err) { console.log(err); } }); + sess.destroy(err => { + if (err) { + console.log(err); + } + }); } - res.redirect("/login"); + res.redirect('/login'); }; export let getForgot = function (req: Request, res: Response) { - res.render("forgot.pug", { - title: "Recover Password", + res.render('forgot.pug', { + title: 'Recover Password', user: req.user, }); }; export let postForgot = function (req: Request, res: Response, next: NextFunction) { const email = req.body.email; - async.waterfall([ - function (done: any) { - c.randomBytes(20, function (err: any, buffer: Buffer) { - if (err) { - done(null); - return; - } - done(null, buffer.toString('hex')); - }); - }, - function (token: string, done: any) { - User.findOne({ email }, function (err: any, user: DashUserModel) { - if (!user) { - // NO ACCOUNT WITH SUBMITTED EMAIL - res.redirect("/forgotPassword"); - return; - } - user.passwordResetToken = token; - user.passwordResetExpires = new Date(Date.now() + 3600000); // 1 HOUR - user.save(function (err: any) { - done(null, token, user); + async.waterfall( + [ + function (done: any) { + c.randomBytes(20, function (err: any, buffer: Buffer) { + if (err) { + done(null); + return; + } + done(null, buffer.toString('hex')); }); - }); - }, - function (token: Uint16Array, user: DashUserModel, done: any) { - const smtpTransport = nodemailer.createTransport({ - service: 'Gmail', - auth: { - user: 'browndashptc@gmail.com', - pass: 'TsarNicholas#2' - } - }); - const mailOptions = { - to: user.email, - from: 'browndashptc@gmail.com', - subject: 'Dash Password Reset', - text: 'You are receiving this because you (or someone else) have requested the reset of the password for your account.\n\n' + - 'Please click on the following link, or paste this into your browser to complete the process:\n\n' + - 'http://' + req.headers.host + '/resetPassword/' + token + '\n\n' + - 'If you did not request this, please ignore this email and your password will remain unchanged.\n' - } as MailOptions; - smtpTransport.sendMail(mailOptions, function (err: Error | null) { - // req.flash('info', 'An e-mail has been sent to ' + user.email + ' with further instructions.'); - done(null, err, 'done'); - }); + }, + function (token: string, done: any) { + User.findOne({ email }, function (err: any, user: DashUserModel) { + if (!user) { + // NO ACCOUNT WITH SUBMITTED EMAIL + res.redirect('/forgotPassword'); + return; + } + user.passwordResetToken = token; + user.passwordResetExpires = new Date(Date.now() + 3600000); // 1 HOUR + user.save(function (err: any) { + done(null, token, user); + }); + }); + }, + function (token: Uint16Array, user: DashUserModel, done: any) { + const smtpTransport = nodemailer.createTransport({ + service: 'Gmail', + auth: { + user: 'browndashptc@gmail.com', + pass: 'TsarNicholas#2', + }, + }); + const mailOptions = { + to: user.email, + from: 'browndashptc@gmail.com', + subject: 'Dash Password Reset', + text: + 'You are receiving this because you (or someone else) have requested the reset of the password for your account.\n\n' + + 'Please click on the following link, or paste this into your browser to complete the process:\n\n' + + 'http://' + + req.headers.host + + '/resetPassword/' + + token + + '\n\n' + + 'If you did not request this, please ignore this email and your password will remain unchanged.\n', + } as MailOptions; + smtpTransport.sendMail(mailOptions, function (err: Error | null) { + // req.flash('info', 'An e-mail has been sent to ' + user.email + ' with further instructions.'); + done(null, err, 'done'); + }); + }, + ], + function (err) { + if (err) return next(err); + res.redirect('/forgotPassword'); } - ], function (err) { - if (err) return next(err); - res.redirect("/forgotPassword"); - }); + ); }; export let getReset = function (req: Request, res: Response) { User.findOne({ passwordResetToken: req.params.token, passwordResetExpires: { $gt: Date.now() } }, function (err: any, user: DashUserModel) { if (!user || err) { - return res.redirect("/forgotPassword"); + return res.redirect('/forgotPassword'); } - res.render("reset.pug", { - title: "Reset Password", + res.render('reset.pug', { + title: 'Reset Password', user: req.user, }); }); }; export let postReset = function (req: Request, res: Response) { - async.waterfall([ - function (done: any) { - User.findOne({ passwordResetToken: req.params.token, passwordResetExpires: { $gt: Date.now() } }, function (err: any, user: DashUserModel) { - if (!user || err) { - return res.redirect('back'); - } + async.waterfall( + [ + function (done: any) { + User.findOne({ passwordResetToken: req.params.token, passwordResetExpires: { $gt: Date.now() } }, function (err: any, user: DashUserModel) { + if (!user || err) { + return res.redirect('back'); + } - req.assert("password", "Password must be at least 4 characters long").len({ min: 4 }); - req.assert("confirmPassword", "Passwords do not match").equals(req.body.password); + req.assert('password', 'Password must be at least 4 characters long').len({ min: 4 }); + req.assert('confirmPassword', 'Passwords do not match').equals(req.body.password); - if (req.validationErrors()) { - return res.redirect('back'); - } + if (req.validationErrors()) { + return res.redirect('back'); + } - user.password = req.body.password; - user.passwordResetToken = undefined; - user.passwordResetExpires = undefined; + user.password = req.body.password; + user.passwordResetToken = undefined; + user.passwordResetExpires = undefined; - user.save(function (err) { - if (err) { - res.redirect("/login"); - return; - } - req.logIn(user, function (err) { + user.save(function (err) { if (err) { + res.redirect('/login'); return; } + req.logIn(user, function (err) { + if (err) { + return; + } + }); + done(null, user); }); - done(null, user); }); - }); - }, - function (user: DashUserModel, done: any) { - const smtpTransport = nodemailer.createTransport({ - service: 'Gmail', - auth: { - user: 'browndashptc@gmail.com', - pass: 'TsarNicholas#2' - } - }); - const mailOptions = { - to: user.email, - from: 'browndashptc@gmail.com', - subject: 'Your password has been changed', - text: 'Hello,\n\n' + - 'This is a confirmation that the password for your account ' + user.email + ' has just been changed.\n' - } as MailOptions; - smtpTransport.sendMail(mailOptions, function (err) { - done(null, err); - }); + }, + function (user: DashUserModel, done: any) { + const smtpTransport = nodemailer.createTransport({ + service: 'Gmail', + auth: { + user: 'browndashptc@gmail.com', + pass: 'TsarNicholas#2', + }, + }); + const mailOptions = { + to: user.email, + from: 'browndashptc@gmail.com', + subject: 'Your password has been changed', + text: 'Hello,\n\n' + 'This is a confirmation that the password for your account ' + user.email + ' has just been changed.\n', + } as MailOptions; + smtpTransport.sendMail(mailOptions, function (err) { + done(null, err); + }); + }, + ], + function (err) { + res.redirect('/login'); } - ], function (err) { - res.redirect("/login"); - }); -};
\ No newline at end of file + ); +}; |