diff options
author | James Hu <51237606+jameshu111@users.noreply.github.com> | 2023-05-04 10:36:59 -0400 |
---|---|---|
committer | James Hu <51237606+jameshu111@users.noreply.github.com> | 2023-05-04 10:36:59 -0400 |
commit | 725bf38dc018cb218d8a88605234e95a2beee446 (patch) | |
tree | 9150905cf1b67b06c03fdd8d10ac6da6e8246832 | |
parent | 96465f2dccc974a821fa912c90def988b76808e5 (diff) | |
parent | 1c24114bbe8f69f61948f7531277305457926498 (diff) |
Merge branch 'master' into james-server-stats
80 files changed, 3599 insertions, 1826 deletions
diff --git a/package-lock.json b/package-lock.json index a26a0ecaa..7d2ef9509 100644 --- a/package-lock.json +++ b/package-lock.json @@ -698,47 +698,40 @@ } }, "@fortawesome/fontawesome-common-types": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.3.0.tgz", - "integrity": "sha512-4BC1NMoacEBzSXRwKjZ/X/gmnbp/HU5Qqat7E8xqorUtBFZS+bwfGH5/wqOC2K6GV0rgEobp3OjGRMa5fK9pFg==" + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.0.tgz", + "integrity": "sha512-HNii132xfomg5QVZw0HwXXpN22s7VBHQBv9CeOu9tfJnhsWQNd2lmTNi8CSrnw5B+5YOmzu1UoPAyxaXsJ6RgQ==" }, "@fortawesome/fontawesome-svg-core": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.3.0.tgz", - "integrity": "sha512-uz9YifyKlixV6AcKlOX8WNdtF7l6nakGyLYxYaCa823bEBqyj/U2ssqtctO38itNEwXb8/lMzjdoJ+aaJuOdrw==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.4.0.tgz", + "integrity": "sha512-Bertv8xOiVELz5raB2FlXDPKt+m94MQ3JgDfsVbrqNpLU9+UE2E18GKjLKw+d3XbeYPqg1pzyQKGsrzbw+pPaw==", "requires": { - "@fortawesome/fontawesome-common-types": "6.3.0" + "@fortawesome/fontawesome-common-types": "6.4.0" } }, "@fortawesome/free-brands-svg-icons": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.3.0.tgz", - "integrity": "sha512-xI0c+a8xnKItAXCN8rZgCNCJQiVAd2Y7p9e2ND6zN3J3ekneu96qrePieJ7yA7073C1JxxoM3vH1RU7rYsaj8w==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.4.0.tgz", + "integrity": "sha512-qvxTCo0FQ5k2N+VCXb/PZQ+QMhqRVM4OORiO6MXdG6bKolIojGU/srQ1ptvKk0JTbRgaJOfL2qMqGvBEZG7Z6g==", "requires": { - "@fortawesome/fontawesome-common-types": "6.3.0" + "@fortawesome/fontawesome-common-types": "6.4.0" } }, "@fortawesome/free-regular-svg-icons": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.3.0.tgz", - "integrity": "sha512-cZnwiVHZ51SVzWHOaNCIA+u9wevZjCuAGSvSYpNlm6A4H4Vhwh8481Bf/5rwheIC3fFKlgXxLKaw8Xeroz8Ntg==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.4.0.tgz", + "integrity": "sha512-ZfycI7D0KWPZtf7wtMFnQxs8qjBXArRzczABuMQqecA/nXohquJ5J/RCR77PmY5qGWkxAZDxpnUFVXKwtY/jPw==", "requires": { - "@fortawesome/fontawesome-common-types": "6.3.0" - }, - "dependencies": { - "@fortawesome/fontawesome-common-types": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.3.0.tgz", - "integrity": "sha512-4BC1NMoacEBzSXRwKjZ/X/gmnbp/HU5Qqat7E8xqorUtBFZS+bwfGH5/wqOC2K6GV0rgEobp3OjGRMa5fK9pFg==" - } + "@fortawesome/fontawesome-common-types": "6.4.0" } }, "@fortawesome/free-solid-svg-icons": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.3.0.tgz", - "integrity": "sha512-x5tMwzF2lTH8pyv8yeZRodItP2IVlzzmBuD1M7BjawWgg9XAvktqJJ91Qjgoaf8qJpHQ8FEU9VxRfOkLhh86QA==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.4.0.tgz", + "integrity": "sha512-kutPeRGWm8V5dltFP1zGjQOEAzaLZj4StdQhWVZnfGFCvAPVvHh8qk5bRrU4KXnRRRNni5tKQI9PBAdI6MP8nQ==", "requires": { - "@fortawesome/fontawesome-common-types": "6.3.0" + "@fortawesome/fontawesome-common-types": "6.4.0" } }, "@fortawesome/react-fontawesome": { @@ -1423,10 +1416,9 @@ } }, "@types/d3-array": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.3.tgz", - "integrity": "sha512-Reoy+pKnvsksN0lQUlcH6dOGjRZ/3WRwXR//m+/8lt1BXeI4xyaUZoqULNjyXXRuh0Mj4LNpkCvhUpQlY3X5xQ==", - "dev": true + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.4.tgz", + "integrity": "sha512-nwvEkG9vYOc0Ic7G7kwgviY4AQlTfYGIZ0fqB7CQHXGyYM6nO7kJh5EguSNA3jfh4rq7Sb7eMVq8isuvg2/miQ==" }, "@types/d3-axis": { "version": "2.1.3", @@ -1437,18 +1429,18 @@ } }, "@types/d3-brush": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.1.tgz", - "integrity": "sha512-B532DozsiTuQMHu2YChdZU0qsFJSio3Q6jmBYGYNp3gMDzBmuFFgPt9qKA4VYuLZMp4qc6eX7IUFUEsvHiXZAw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.2.tgz", + "integrity": "sha512-2TEm8KzUG3N7z0TrSKPmbxByBx54M+S9lHoP2J55QuLU0VSQ9mE96EJSAOVNEqd1bbynMjeTS9VHmz8/bSw8rA==", "dev": true, "requires": { "@types/d3-selection": "*" } }, "@types/d3-chord": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.1.tgz", - "integrity": "sha512-eQfcxIHrg7V++W8Qxn6QkqBNBokyhdWSAS73AbkbMzvLQmVVBviknoz2SRS/ZJdIOmhcmmdCRE/NFOm28Z1AMw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.2.tgz", + "integrity": "sha512-abT/iLHD3sGZwqMTX1TYCMEulr+wBd0SzyOQnjYNLp7sngdOHYtNkMRI5v3w5thoN+BWtlHVDx2Osvq6fxhZWw==", "dev": true }, "@types/d3-color": { @@ -1457,9 +1449,9 @@ "integrity": "sha512-+0EtEjBfKEDtH9Rk3u3kLOUXM5F+iZK+WvASPb0MhIZl8J8NUvGeZRwKCXl+P3HkYx5TdU4YtcibpqHkSR9n7w==" }, "@types/d3-contour": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.1.tgz", - "integrity": "sha512-C3zfBrhHZvrpAAK3YXqLWVAGo87A4SvJ83Q/zVJ8rFWJdKejUnDYaWZPkA8K84kb2vDA/g90LTQAz7etXcgoQQ==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.2.tgz", + "integrity": "sha512-k6/bGDoAGJZnZWaKzeB+9glgXCYGvh6YlluxzBREiVo8f/X2vpTEdgPy9DN7Z2i42PZOZ4JDhVdlTSTSkLDPlQ==", "dev": true, "requires": { "@types/d3-array": "*", @@ -1473,45 +1465,44 @@ "dev": true }, "@types/d3-dispatch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.1.tgz", - "integrity": "sha512-NhxMn3bAkqhjoxabVJWKryhnZXXYYVQxaBnbANu0O94+O/nX9qSjrA1P1jbAQJxJf+VC72TxDX/YJcKue5bRqw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.2.tgz", + "integrity": "sha512-rxN6sHUXEZYCKV05MEh4z4WpPSqIw+aP7n9ZN6WYAAvZoEAghEK1WeVZMZcHRBwyaKflU43PCUAJNjFxCzPDjg==", "dev": true }, "@types/d3-drag": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.1.tgz", - "integrity": "sha512-o1Va7bLwwk6h03+nSM8dpaGEYnoIG19P0lKqlic8Un36ymh9NSkNFX1yiXMKNMx8rJ0Kfnn2eovuFaL6Jvj0zA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.2.tgz", + "integrity": "sha512-qmODKEDvyKWVHcWWCOVcuVcOwikLVsyc4q4EBJMREsoQnR2Qoc2cZQUyFUPgO9q4S3qdSqJKBsuefv+h0Qy+tw==", "dev": true, "requires": { "@types/d3-selection": "*" } }, "@types/d3-dsv": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.0.tgz", - "integrity": "sha512-o0/7RlMl9p5n6FQDptuJVMxDf/7EDEv2SYEO/CwdG2tr1hTfUVi0Iavkk2ax+VpaQ/1jVhpnj5rq1nj8vwhn2A==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-76pBHCMTvPLt44wFOieouXcGXWOF0AJCceUvaFkxSZEu4VDUdv93JfpMa6VGNFs01FHfuP4a5Ou68eRG1KBfTw==", "dev": true }, "@types/d3-ease": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.0.tgz", - "integrity": "sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA==", - "dev": true + "integrity": "sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA==" }, "@types/d3-fetch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.1.tgz", - "integrity": "sha512-toZJNOwrOIqz7Oh6Q7l2zkaNfXkfR7mFSJvGvlD/Ciq/+SQ39d5gynHJZ/0fjt83ec3WL7+u3ssqIijQtBISsw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.2.tgz", + "integrity": "sha512-gllwYWozWfbep16N9fByNBDTkJW/SyhH6SGRlXloR7WdtAaBui4plTP+gbUgiEot7vGw/ZZop1yDZlgXXSuzjA==", "dev": true, "requires": { "@types/d3-dsv": "*" } }, "@types/d3-force": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.3.tgz", - "integrity": "sha512-z8GteGVfkWJMKsx6hwC3SiTSLspL98VNpmvLpEFJQpZPq6xpA1I8HNBDNSpukfK0Vb0l64zGFhzunLgEAcBWSA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.4.tgz", + "integrity": "sha512-q7xbVLrWcXvSBBEoadowIUJ7sRpS1yvgMWnzHJggFy5cUZBq2HZL5k/pBSm0GdYWS1vs5/EDwMjSKF55PDY4Aw==", "dev": true }, "@types/d3-format": { @@ -1521,25 +1512,24 @@ "dev": true }, "@types/d3-geo": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.0.2.tgz", - "integrity": "sha512-DbqK7MLYA8LpyHQfv6Klz0426bQEf7bRTvhMy44sNGVyZoWn//B0c+Qbeg8Osi2Obdc9BLLXYAKpyWege2/7LQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.0.3.tgz", + "integrity": "sha512-bK9uZJS3vuDCNeeXQ4z3u0E7OeJZXjUgzFdSOtNtMCJCLvDtWDwfpRVWlyt3y8EvRzI0ccOu9xlMVirawolSCw==", "dev": true, "requires": { "@types/geojson": "*" } }, "@types/d3-hierarchy": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.0.tgz", - "integrity": "sha512-g+sey7qrCa3UbsQlMZZBOHROkFqx7KZKvUpRzI/tAp/8erZWpYq7FgNKvYwebi2LaEiVs1klhUfd3WCThxmmWQ==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-9hjRTVoZjRFR6xo8igAJyNXQyPX6Aq++Nhb5ebrUF414dv4jr2MitM2fWiOY475wa3Za7TOS2Gh9fmqEhLTt0A==", "dev": true }, "@types/d3-interpolate": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz", "integrity": "sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==", - "dev": true, "requires": { "@types/d3-color": "*" } @@ -1547,8 +1537,7 @@ "@types/d3-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.0.tgz", - "integrity": "sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg==", - "dev": true + "integrity": "sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg==" }, "@types/d3-polygon": { "version": "3.0.0", @@ -1588,10 +1577,9 @@ "integrity": "sha512-3mhtPnGE+c71rl/T5HMy+ykg7migAZ4T6gzU0HxpgBFKcasBrSnwRbYV1/UZR6o5fkpySxhWxAhd7yhjj8jL7g==" }, "@types/d3-shape": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.0.tgz", - "integrity": "sha512-jYIYxFFA9vrJ8Hd4Se83YI6XF+gzDL1aC5DCsldai4XYYiVNdhtpGbA/GM6iyQ8ayhSp3a148LY34hy7A4TxZA==", - "dev": true, + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.1.tgz", + "integrity": "sha512-6Uh86YFF7LGg4PQkuO2oG6EMBRLuW9cbavUW46zkIO5kuS2PfTqo2o9SkgtQzguBHbLgNnU90UNsITpsX1My+A==", "requires": { "@types/d3-path": "*" } @@ -1610,8 +1598,7 @@ "@types/d3-timer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.0.tgz", - "integrity": "sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g==", - "dev": true + "integrity": "sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g==" }, "@types/d3-transition": { "version": "3.0.3", @@ -2322,6 +2309,15 @@ "@types/react": "*" } }, + "@types/react-typist": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/react-typist/-/react-typist-2.0.3.tgz", + "integrity": "sha512-5eDrikVNJ73qR5XEV+ZNApoYarzYUD5OOPdNwnNY5RtqituZl9haq0OdZUaac2R751QSOe3zDwnqYhBmdFhG7g==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/reactcss": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@types/reactcss/-/reactcss-1.2.6.tgz", @@ -2456,7 +2452,7 @@ "@types/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I=", + "integrity": "sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==", "dev": true }, "@types/strip-json-comments": { @@ -2807,7 +2803,7 @@ "textarea-caret": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/textarea-caret/-/textarea-caret-3.0.2.tgz", - "integrity": "sha512-gRzeti2YS4did7UJnPQ47wrjD+vp+CJIe9zbsu0bJ987d8QVLvLNG9757rqiQTIy4hGIeFauTTJt5Xkn51UkXg==" + "integrity": "sha1-82DEhpmqGr9xhoCkOjGoUGZcLK8=" } } }, @@ -2906,7 +2902,7 @@ "after": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", - "integrity": "sha512-QbJ0NTQ/I9DI3uSJA4cbexiwQeRAfjPScqIbSjUDd9TOrcg6pTkdgziesOqxBMBzit8vFCTwrP27t13vFOORRA==" + "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" }, "agent-base": { "version": "6.0.2", @@ -3803,7 +3799,7 @@ "backo2": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha512-zj6Z6M7Eq+PBZ7PQxl5NT665MvJdAkzp0f60nAJ+sLaSCBPMwVak5ZegFbgVCzFcCJTKFoMizvM5Ld7+JrRJHA==" + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" }, "bail": { "version": "2.0.2", @@ -3873,7 +3869,7 @@ "base64-arraybuffer": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz", - "integrity": "sha512-a1eIFi4R9ySrbiMuyTGx5e92uRH5tQY6kArNcFaKBUleIoLjdjBg7Zxm3Mqm3Kmkf27HLR/1fnxX9q8GQ7Iavg==" + "integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=" }, "base64-js": { "version": "1.5.1", @@ -4509,9 +4505,9 @@ "dev": true }, "chart.js": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.8.0.tgz", - "integrity": "sha512-cr8xhrXjLIXVLOBZPkBZVF6NDeiVIrPLHcMhnON7UufudL+CNeRrD+wpYanswlm8NpudMdrt3CHoLMQMxJhHRg==" + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.9.1.tgz", + "integrity": "sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w==" }, "check-error": { "version": "1.0.2", @@ -4846,7 +4842,7 @@ "component-bind": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", - "integrity": "sha512-WZveuKPeKAG9qY+FkYDeADzdHyTYdIboXS59ixDeRJL5ZhxpqUnxSOwop4FQjMsiYm3/Or8cegVbpAHNA7pHxw==" + "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" }, "component-emitter": { "version": "1.3.0", @@ -4856,7 +4852,7 @@ "component-inherit": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", - "integrity": "sha512-w+LhYREhatpVqTESyGFg3NlP6Iu0kEKUHETY9GoZP/pQyW4mHFZuFWRUCIqVPZ36ueVLtoOEZaAqbCF2RDndaA==" + "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=" }, "compress-commons": { "version": "2.1.1", @@ -5576,7 +5572,7 @@ "custom-event": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", - "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==" + "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=" }, "cyclist": { "version": "1.0.1", @@ -5654,25 +5650,6 @@ "d3-interpolate": "1 - 3", "d3-selection": "3", "d3-transition": "3" - }, - "dependencies": { - "d3-selection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", - "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==" - }, - "d3-transition": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", - "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", - "requires": { - "d3-color": "1 - 3", - "d3-dispatch": "1 - 3", - "d3-ease": "1 - 3", - "d3-interpolate": "1 - 3", - "d3-timer": "1 - 3" - } - } } }, "d3-chord": { @@ -5689,27 +5666,17 @@ "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==" }, "d3-contour": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.0.tgz", - "integrity": "sha512-7aQo0QHUTu/Ko3cP9YK9yUTxtoDEiDGwnBHyLxG5M4vqlBkO/uixMRele3nfsfj6UXOcuReVpVXzAboGraYIJw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", "requires": { "d3-array": "^3.2.0" - }, - "dependencies": { - "d3-array": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.0.tgz", - "integrity": "sha512-3yXFQo0oG3QCxbF06rMPFyGRMGJNS7NvsV1+2joOjbBE+9xvWQ8+GcMJAjRCzw06zQ3/arXeJgbPYcjUCuC+3g==", - "requires": { - "internmap": "1 - 2" - } - } } }, "d3-delaunay": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.2.tgz", - "integrity": "sha512-IMLNldruDQScrcfT+MWnazhHbDJhcRJyOEBAJfwQnHle1RPh6WDuLvxNArUju2VSMSUuKlY5BGHRJ2cYyoFLQQ==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", "requires": { "delaunator": "5" } @@ -5774,9 +5741,9 @@ "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==" }, "d3-geo": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.0.1.tgz", - "integrity": "sha512-Wt23xBych5tSy9IYAM1FR2rWIBFWa52B/oF/GYe5zbdHrg08FU8+BuI6X4PvTwPDdqdAdq04fuWJpELtsaEjeA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==", "requires": { "d3-array": "2.5.0 - 3" } @@ -6689,7 +6656,7 @@ "dynamic-dedupe": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", - "integrity": "sha1-BuRMIj9eTpTXjvnbI6ZRXOL5YqE=", + "integrity": "sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==", "dev": true, "requires": { "xtend": "^4.0.0" @@ -6819,7 +6786,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "ws": { "version": "7.4.6", @@ -6861,8 +6828,8 @@ "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==" }, "equation-editor-react": { - "version": "git+ssh://git@github.com/bobzel/equation-editor-react.git#75915e852b4b36a6a4cd3e1cbc80598da6b65227", - "from": "equation-editor-react@github:bobzel/equation-editor-react#useLocally", + "version": "github:bobzel/equation-editor-react#75915e852b4b36a6a4cd3e1cbc80598da6b65227", + "from": "github:bobzel/equation-editor-react#useLocally", "requires": { "jquery": "^3.4.1", "mathquill": "^0.10.1-a" @@ -8836,9 +8803,9 @@ "dev": true }, "fast-equals": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-2.0.4.tgz", - "integrity": "sha512-caj/ZmjHljPrZtbzJ3kfH5ia/k4mTJe/qSiXAGzxZWRZgsgDV0cvNaQULqUX8t0/JVlzzEdYOwCN5DmzTxoD4w==" + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-4.0.3.tgz", + "integrity": "sha512-G3BSX9cfKttjr+2o1O22tYMLq0DPluZnYtq1rXumE1SpL/F/SLIfHx08WYQoWSIpeMYf8sRbJ8++71+v6Pnxfg==" }, "fast-json-stable-stringify": { "version": "2.1.0", @@ -9349,7 +9316,7 @@ "fs-extra": { "version": "0.26.7", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.26.7.tgz", - "integrity": "sha1-muH92UiXeY7at20JGM9C0MMYT6k=", + "integrity": "sha512-waKu+1KumRhYv8D8gMRCKJGAMI9pRnPuEb1mvgYD0f7wBscg+h6bW4FDTmEZhB9VKxvoTtxW+Y7bnIlB7zja6Q==", "requires": { "graceful-fs": "^4.1.2", "jsonfile": "^2.1.0", @@ -10009,14 +9976,14 @@ "isarray": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ==" + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" } } }, "has-cors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", - "integrity": "sha512-g5VNKdkFuUuVCP9gYfDJHjK2nqdQJ7aDLTnycnc2+RvsOQbuLdF5pm7vuE5J76SEBIQjs4kQY/BWq74JUmjbXA==" + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" }, "has-flag": { "version": "3.0.0", @@ -10592,7 +10559,7 @@ "indexof": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg==" + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" }, "inflight": { "version": "1.0.6", @@ -11703,7 +11670,7 @@ "jsonfile": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", + "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==", "requires": { "graceful-fs": "^4.1.6" } @@ -12126,7 +12093,7 @@ "lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" }, "lodash.isplainobject": { "version": "4.0.6", @@ -12387,7 +12354,7 @@ "mathquill": { "version": "0.10.1-a", "resolved": "https://registry.npmjs.org/mathquill/-/mathquill-0.10.1-a.tgz", - "integrity": "sha512-snSAEwAtwdwBFSor+nVBnWWQtTw67kgAgKMyAIxuz4ZPboy0qkWZmd7BL3lfOXp/INihhRlU1PcfaAtDaRhmzA==", + "integrity": "sha1-vyylaQEAY6w0vNXVKa3Ag3zVPD8=", "requires": { "jquery": "^1.12.3" }, @@ -12395,7 +12362,7 @@ "jquery": { "version": "1.12.4", "resolved": "https://registry.npmjs.org/jquery/-/jquery-1.12.4.tgz", - "integrity": "sha512-UEVp7PPK9xXYSk8xqXCJrkXnKZtlgWkd2GsAQbMRFK6S/ePU2JN5G2Zum8hIVjzR3CpdfSqdqAzId/xd4TJHeg==" + "integrity": "sha1-AeHfuikP5z3rp3zurLD5ui/sngw=" } } }, @@ -14081,7 +14048,8 @@ "dependencies": { "@iarna/cli": { "version": "2.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/@iarna/cli/-/cli-2.1.0.tgz", + "integrity": "sha512-rvVVqDa2g860niRbqs3D5RhL4la3dc1vwk+NlpKPZxKaMSHtE2se6C2x8NeveN+rcjp3/686X+u+09CZ+7lmAQ==", "requires": { "glob": "^7.1.2", "signal-exit": "^3.0.2" @@ -14089,7 +14057,8 @@ }, "JSONStream": { "version": "1.3.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", "requires": { "jsonparse": "^1.2.0", "through": ">=2.2.7 <3" @@ -14097,59 +14066,70 @@ }, "abbrev": { "version": "1.1.1", - "bundled": true + "resolved": false, + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "agent-base": { "version": "4.3.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", "requires": { "es6-promisify": "^5.0.0" } }, "agentkeepalive": { "version": "3.5.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==", "requires": { "humanize-ms": "^1.2.1" } }, "ansi-align": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", "requires": { "string-width": "^2.0.0" } }, "ansi-regex": { "version": "2.1.1", - "bundled": true + "resolved": false, + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "ansi-styles": { "version": "3.2.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "requires": { "color-convert": "^1.9.0" } }, "ansicolors": { "version": "0.3.2", - "bundled": true + "resolved": false, + "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=" }, "ansistyles": { "version": "0.1.3", - "bundled": true + "resolved": false, + "integrity": "sha1-XeYEFb2gcbs3EnhUyGT0GyMlRTk=" }, "aproba": { "version": "2.0.0", - "bundled": true + "resolved": false, + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" }, "archy": { "version": "1.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=" }, "are-we-there-yet": { "version": "1.1.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", "requires": { "delegates": "^1.0.0", "readable-stream": "^2.0.6" @@ -14157,7 +14137,8 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -14170,20 +14151,23 @@ "dependencies": { "safe-buffer": { "version": "5.1.2", - "bundled": true + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } }, "string_decoder": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" }, "dependencies": { "safe-buffer": { "version": "5.1.2", - "bundled": true + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } } @@ -14191,45 +14175,54 @@ }, "asap": { "version": "2.0.6", - "bundled": true + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" }, "asn1": { "version": "0.2.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", "requires": { "safer-buffer": "~2.1.0" } }, "assert-plus": { "version": "1.0.0", - "bundled": true + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==" }, "asynckit": { "version": "0.4.0", - "bundled": true + "resolved": false, + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "aws-sign2": { "version": "0.7.0", - "bundled": true + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==" }, "aws4": { "version": "1.11.0", - "bundled": true + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" }, "balanced-match": { "version": "1.0.2", - "bundled": true + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "bcrypt-pbkdf": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", "requires": { "tweetnacl": "^0.14.3" } }, "bin-links": { "version": "1.1.8", - "bundled": true, + "resolved": false, + "integrity": "sha512-KgmVfx+QqggqP9dA3iIc5pA4T1qEEEL+hOhOhNPaUm77OTrJoOXE/C05SJLNJe6m/2wUK7F1tDSou7n5TfCDzQ==", "requires": { "bluebird": "^3.5.3", "cmd-shim": "^3.0.0", @@ -14241,11 +14234,13 @@ }, "bluebird": { "version": "3.7.2", - "bundled": true + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, "boxen": { "version": "1.3.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", "requires": { "ansi-align": "^2.0.0", "camelcase": "^4.0.0", @@ -14258,7 +14253,8 @@ }, "brace-expansion": { "version": "1.1.11", - "bundled": true, + "resolved": false, + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -14266,23 +14262,28 @@ }, "buffer-from": { "version": "1.0.0", - "bundled": true + "resolved": false, + "integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA==" }, "builtins": { "version": "1.0.3", - "bundled": true + "resolved": false, + "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=" }, "byline": { "version": "5.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE=" }, "byte-size": { "version": "5.0.1", - "bundled": true + "resolved": false, + "integrity": "sha512-/XuKeqWocKsYa/cBY1YbSJSWWqTi4cFgr9S6OyM7PBaPbr9zvNGwWP33vt0uqGhwDdN+y3yhbXVILEUpnwEWGw==" }, "cacache": { "version": "12.0.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", + "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", "requires": { "bluebird": "^3.5.5", "chownr": "^1.1.1", @@ -14303,23 +14304,28 @@ }, "call-limit": { "version": "1.1.1", - "bundled": true + "resolved": false, + "integrity": "sha512-5twvci5b9eRBw2wCfPtN0GmlR2/gadZqyFpPhOK6CvMFoFgA+USnZ6Jpu1lhG9h85pQ3Ouil3PfXWRD4EUaRiQ==" }, "camelcase": { "version": "4.1.0", - "bundled": true + "resolved": false, + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" }, "capture-stack-trace": { "version": "1.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0=" }, "caseless": { "version": "0.12.0", - "bundled": true + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" }, "chalk": { "version": "2.4.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -14328,26 +14334,31 @@ }, "chownr": { "version": "1.1.4", - "bundled": true + "resolved": false, + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" }, "ci-info": { "version": "2.0.0", - "bundled": true + "resolved": false, + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" }, "cidr-regex": { "version": "2.0.10", - "bundled": true, + "resolved": false, + "integrity": "sha512-sB3ogMQXWvreNPbJUZMRApxuRYd+KoIo4RGQ81VatjmMW6WJPo+IJZ2846FGItr9VzKo5w7DXzijPLGtSd0N3Q==", "requires": { "ip-regex": "^2.1.0" } }, "cli-boxes": { "version": "1.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=" }, "cli-columns": { "version": "3.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-ZzLZcpee/CrkRKHwjgj6E5yWoY4=", "requires": { "string-width": "^2.0.0", "strip-ansi": "^3.0.1" @@ -14355,7 +14366,8 @@ }, "cli-table3": { "version": "0.5.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==", "requires": { "colors": "^1.1.2", "object-assign": "^4.1.0", @@ -14364,7 +14376,8 @@ }, "cliui": { "version": "5.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", "requires": { "string-width": "^3.1.0", "strip-ansi": "^5.2.0", @@ -14373,15 +14386,18 @@ "dependencies": { "ansi-regex": { "version": "4.1.1", - "bundled": true + "resolved": false, + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==" }, "is-fullwidth-code-point": { "version": "2.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, "string-width": { "version": "3.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "requires": { "emoji-regex": "^7.0.1", "is-fullwidth-code-point": "^2.0.0", @@ -14390,7 +14406,8 @@ }, "strip-ansi": { "version": "5.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "requires": { "ansi-regex": "^4.1.0" } @@ -14399,11 +14416,13 @@ }, "clone": { "version": "1.0.4", - "bundled": true + "resolved": false, + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" }, "cmd-shim": { "version": "3.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-DtGg+0xiFhQIntSBRzL2fRQBnmtAVwXIDo4Qq46HPpObYquxMaZS4sb82U9nH91qJrlosC1wa9gwr0QyL/HypA==", "requires": { "graceful-fs": "^4.1.2", "mkdirp": "~0.5.0" @@ -14411,27 +14430,32 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true + "resolved": false, + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "color-convert": { "version": "1.9.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", "requires": { "color-name": "^1.1.1" } }, "color-name": { "version": "1.1.3", - "bundled": true + "resolved": false, + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "colors": { "version": "1.3.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==", "optional": true }, "columnify": { "version": "1.5.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs=", "requires": { "strip-ansi": "^3.0.0", "wcwidth": "^1.0.0" @@ -14439,18 +14463,21 @@ }, "combined-stream": { "version": "1.0.8", - "bundled": true, + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "requires": { "delayed-stream": "~1.0.0" } }, "concat-map": { "version": "0.0.1", - "bundled": true + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "concat-stream": { "version": "1.6.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", "requires": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", @@ -14460,7 +14487,8 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -14473,20 +14501,23 @@ "dependencies": { "safe-buffer": { "version": "5.1.2", - "bundled": true + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } }, "string_decoder": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" }, "dependencies": { "safe-buffer": { "version": "5.1.2", - "bundled": true + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } } @@ -14494,7 +14525,8 @@ }, "config-chain": { "version": "1.1.13", - "bundled": true, + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", "requires": { "ini": "^1.3.4", "proto-list": "~1.2.1" @@ -14502,7 +14534,8 @@ }, "configstore": { "version": "3.1.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-nlOhI4+fdzoK5xmJ+NY+1gZK56bwEaWZr8fYuXohZ9Vkc1o3a4T/R3M+yE/w7x/ZVJ1zF8c+oaOvF0dztdUgmA==", "requires": { "dot-prop": "^4.2.1", "graceful-fs": "^4.1.2", @@ -14514,11 +14547,13 @@ }, "console-control-strings": { "version": "1.1.0", - "bundled": true + "resolved": false, + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, "copy-concurrently": { "version": "1.0.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", "requires": { "aproba": "^1.1.1", "fs-write-stream-atomic": "^1.0.8", @@ -14530,28 +14565,33 @@ "dependencies": { "aproba": { "version": "1.2.0", - "bundled": true + "resolved": false, + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, "iferr": { "version": "0.1.5", - "bundled": true + "resolved": false, + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=" } } }, "core-util-is": { "version": "1.0.2", - "bundled": true + "resolved": false, + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "create-error-class": { "version": "3.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", "requires": { "capture-stack-trace": "^1.0.0" } }, "cross-spawn": { "version": "5.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "requires": { "lru-cache": "^4.0.1", "shebang-command": "^1.2.0", @@ -14560,7 +14600,8 @@ "dependencies": { "lru-cache": { "version": "4.1.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", "requires": { "pseudomap": "^1.0.2", "yallist": "^2.1.2" @@ -14568,87 +14609,104 @@ }, "yallist": { "version": "2.1.2", - "bundled": true + "resolved": false, + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" } } }, "crypto-random-string": { "version": "1.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=" }, "cyclist": { "version": "0.2.2", - "bundled": true + "resolved": false, + "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=" }, "dashdash": { "version": "1.14.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", "requires": { "assert-plus": "^1.0.0" } }, "debug": { "version": "3.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "requires": { "ms": "2.0.0" }, "dependencies": { "ms": { "version": "2.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" } } }, "debuglog": { "version": "1.0.1", - "bundled": true + "resolved": false, + "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=" }, "decamelize": { "version": "1.2.0", - "bundled": true + "resolved": false, + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, "decode-uri-component": { "version": "0.2.2", - "bundled": true + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==" }, "deep-extend": { "version": "0.6.0", - "bundled": true + "resolved": false, + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" }, "defaults": { "version": "1.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", "requires": { "clone": "^1.0.2" } }, "define-properties": { "version": "1.1.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", "requires": { "object-keys": "^1.0.12" } }, "delayed-stream": { "version": "1.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "delegates": { "version": "1.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, "detect-indent": { "version": "5.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50=" }, "detect-newline": { "version": "2.1.0", - "bundled": true + "resolved": false, + "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=" }, "dezalgo": { "version": "1.0.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", "requires": { "asap": "^2.0.0", "wrappy": "1" @@ -14656,22 +14714,26 @@ }, "dot-prop": { "version": "4.2.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-l0p4+mIuJIua0mhxGoh4a+iNL9bmeK5DvnSVQa6T0OhrVmaEa1XScX5Etc673FePCJOArq/4Pa2cLGODUWTPOQ==", "requires": { "is-obj": "^1.0.0" } }, "dotenv": { "version": "5.0.1", - "bundled": true + "resolved": false, + "integrity": "sha512-4As8uPrjfwb7VXC+WnLCbXK7y+Ueb2B3zgNCePYfhxS1PYeaO1YTeplffTEcbfLhvFNGLAz90VvJs9yomG7bow==" }, "duplexer3": { "version": "0.1.4", - "bundled": true + "resolved": false, + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" }, "duplexify": { "version": "3.6.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-fO3Di4tBKJpYTFHAxTU00BcfWMY9w24r/x21a6rZRbsD/ToUgGxsMbiGRmB7uVAXeGKXD9MwiLZa5E97EVgIRQ==", "requires": { "end-of-stream": "^1.0.0", "inherits": "^2.0.1", @@ -14681,7 +14743,8 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -14694,20 +14757,23 @@ "dependencies": { "safe-buffer": { "version": "5.1.2", - "bundled": true + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } }, "string_decoder": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" }, "dependencies": { "safe-buffer": { "version": "5.1.2", - "bundled": true + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } } @@ -14715,7 +14781,8 @@ }, "ecc-jsbn": { "version": "0.1.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", "requires": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" @@ -14723,44 +14790,52 @@ }, "editor": { "version": "1.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-YMf4e9YrzGqJT6jM1q+3gjok90I=" }, "emoji-regex": { "version": "7.0.3", - "bundled": true + "resolved": false, + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" }, "encoding": { "version": "0.1.12", - "bundled": true, + "resolved": false, + "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", "requires": { "iconv-lite": "~0.4.13" } }, "end-of-stream": { "version": "1.4.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", "requires": { "once": "^1.4.0" } }, "env-paths": { "version": "2.2.1", - "bundled": true + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==" }, "err-code": { "version": "1.1.2", - "bundled": true + "resolved": false, + "integrity": "sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA=" }, "errno": { "version": "0.1.7", - "bundled": true, + "resolved": false, + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", "requires": { "prr": "~1.0.1" } }, "es-abstract": { "version": "1.12.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", "requires": { "es-to-primitive": "^1.1.1", "function-bind": "^1.1.1", @@ -14771,7 +14846,8 @@ }, "es-to-primitive": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", "requires": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -14780,22 +14856,26 @@ }, "es6-promise": { "version": "4.2.8", - "bundled": true + "resolved": false, + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" }, "es6-promisify": { "version": "5.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", "requires": { "es6-promise": "^4.0.3" } }, "escape-string-regexp": { "version": "1.0.5", - "bundled": true + "resolved": false, + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "execa": { "version": "0.7.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", "requires": { "cross-spawn": "^5.0.1", "get-stream": "^3.0.0", @@ -14808,37 +14888,45 @@ "dependencies": { "get-stream": { "version": "3.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" } } }, "extend": { "version": "3.0.2", - "bundled": true + "resolved": false, + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "extsprintf": { "version": "1.3.0", - "bundled": true + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==" }, "fast-json-stable-stringify": { "version": "2.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" }, "figgy-pudding": { "version": "3.5.2", - "bundled": true + "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", + "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==" }, "filter-obj": { "version": "1.1.0", - "bundled": true + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==" }, "find-npm-prefix": { "version": "1.0.2", - "bundled": true + "resolved": false, + "integrity": "sha512-KEftzJ+H90x6pcKtdXZEPsQse8/y/UnvzRKrOSQFprnrGaFuJ62fVkP34Iu2IYuMvyauCyoLTNkJZgrrGA2wkA==" }, "flush-write-stream": { "version": "1.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==", "requires": { "inherits": "^2.0.1", "readable-stream": "^2.0.4" @@ -14846,7 +14934,8 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -14859,20 +14948,23 @@ "dependencies": { "safe-buffer": { "version": "5.1.2", - "bundled": true + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } }, "string_decoder": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" }, "dependencies": { "safe-buffer": { "version": "5.1.2", - "bundled": true + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } } @@ -14880,11 +14972,13 @@ }, "forever-agent": { "version": "0.6.1", - "bundled": true + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==" }, "form-data": { "version": "2.3.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", @@ -14893,7 +14987,8 @@ }, "from2": { "version": "2.3.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", "requires": { "inherits": "^2.0.1", "readable-stream": "^2.0.0" @@ -14901,7 +14996,8 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -14914,20 +15010,23 @@ "dependencies": { "safe-buffer": { "version": "5.1.2", - "bundled": true + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } }, "string_decoder": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" }, "dependencies": { "safe-buffer": { "version": "5.1.2", - "bundled": true + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } } @@ -14935,14 +15034,16 @@ }, "fs-minipass": { "version": "1.2.7", - "bundled": true, + "resolved": false, + "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", "requires": { "minipass": "^2.6.0" }, "dependencies": { "minipass": { "version": "2.9.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -14952,7 +15053,8 @@ }, "fs-vacuum": { "version": "1.2.10", - "bundled": true, + "resolved": false, + "integrity": "sha1-t2Kb7AekAxolSP35n17PHMizHjY=", "requires": { "graceful-fs": "^4.1.2", "path-is-inside": "^1.0.1", @@ -14961,7 +15063,8 @@ }, "fs-write-stream-atomic": { "version": "1.0.10", - "bundled": true, + "resolved": false, + "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", "requires": { "graceful-fs": "^4.1.2", "iferr": "^0.1.5", @@ -14971,11 +15074,13 @@ "dependencies": { "iferr": { "version": "0.1.5", - "bundled": true + "resolved": false, + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=" }, "readable-stream": { "version": "2.3.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -14988,20 +15093,23 @@ "dependencies": { "safe-buffer": { "version": "5.1.2", - "bundled": true + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } }, "string_decoder": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" }, "dependencies": { "safe-buffer": { "version": "5.1.2", - "bundled": true + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } } @@ -15009,15 +15117,18 @@ }, "fs.realpath": { "version": "1.0.0", - "bundled": true + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "function-bind": { "version": "1.1.1", - "bundled": true + "resolved": false, + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "gauge": { "version": "2.7.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "requires": { "aproba": "^1.0.3", "console-control-strings": "^1.0.0", @@ -15031,11 +15142,13 @@ "dependencies": { "aproba": { "version": "1.2.0", - "bundled": true + "resolved": false, + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, "string-width": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -15046,11 +15159,13 @@ }, "genfun": { "version": "5.0.0", - "bundled": true + "resolved": false, + "integrity": "sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA==" }, "gentle-fs": { "version": "2.3.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-OlwBBwqCFPcjm33rF2BjW+Pr6/ll2741l+xooiwTCeaX2CA1ZuclavyMBe0/KlR21/XGsgY6hzEQZ15BdNa13Q==", "requires": { "aproba": "^1.1.2", "chownr": "^1.1.2", @@ -15067,35 +15182,41 @@ "dependencies": { "aproba": { "version": "1.2.0", - "bundled": true + "resolved": false, + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, "iferr": { "version": "0.1.5", - "bundled": true + "resolved": false, + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=" } } }, "get-caller-file": { "version": "2.0.5", - "bundled": true + "resolved": false, + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, "get-stream": { "version": "4.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", "requires": { "pump": "^3.0.0" } }, "getpass": { "version": "0.1.7", - "bundled": true, + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", "requires": { "assert-plus": "^1.0.0" } }, "glob": { "version": "7.2.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -15107,7 +15228,8 @@ "dependencies": { "minimatch": { "version": "3.1.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "requires": { "brace-expansion": "^1.1.7" } @@ -15116,14 +15238,16 @@ }, "global-dirs": { "version": "0.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", "requires": { "ini": "^1.3.4" } }, "got": { "version": "6.7.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", "requires": { "create-error-class": "^3.0.0", "duplexer3": "^0.1.4", @@ -15140,21 +15264,25 @@ "dependencies": { "get-stream": { "version": "3.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" } } }, "graceful-fs": { "version": "4.2.10", - "bundled": true + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, "har-schema": { "version": "2.0.0", - "bundled": true + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==" }, "har-validator": { "version": "5.1.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", "requires": { "ajv": "^6.12.3", "har-schema": "^2.0.0" @@ -15162,7 +15290,8 @@ "dependencies": { "ajv": { "version": "6.12.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -15172,44 +15301,53 @@ }, "fast-deep-equal": { "version": "3.1.3", - "bundled": true + "resolved": false, + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "json-schema-traverse": { "version": "0.4.1", - "bundled": true + "resolved": false, + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" } } }, "has": { "version": "1.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "requires": { "function-bind": "^1.1.1" } }, "has-flag": { "version": "3.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "has-symbols": { "version": "1.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=" }, "has-unicode": { "version": "2.0.1", - "bundled": true + "resolved": false, + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" }, "hosted-git-info": { "version": "2.8.9", - "bundled": true + "resolved": false, + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" }, "http-cache-semantics": { "version": "3.8.1", - "bundled": true + "resolved": false, + "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==" }, "http-proxy-agent": { "version": "2.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", "requires": { "agent-base": "4", "debug": "3.1.0" @@ -15217,7 +15355,8 @@ }, "http-signature": { "version": "1.2.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", "requires": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", @@ -15226,7 +15365,8 @@ }, "https-proxy-agent": { "version": "2.2.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", "requires": { "agent-base": "^4.3.0", "debug": "^3.1.0" @@ -15234,44 +15374,52 @@ }, "humanize-ms": { "version": "1.2.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", "requires": { "ms": "^2.0.0" } }, "iconv-lite": { "version": "0.4.23", - "bundled": true, + "resolved": false, + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", "requires": { "safer-buffer": ">= 2.1.2 < 3" } }, "iferr": { "version": "1.0.2", - "bundled": true + "resolved": false, + "integrity": "sha512-9AfeLfji44r5TKInjhz3W9DyZI1zR1JAf2hVBMGhddAKPqBsupb89jGfbCTHIGZd6fGZl9WlHdn4AObygyMKwg==" }, "ignore-walk": { "version": "3.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", "requires": { "minimatch": "^3.0.4" } }, "import-lazy": { "version": "2.1.0", - "bundled": true + "resolved": false, + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=" }, "imurmurhash": { "version": "0.1.4", - "bundled": true + "resolved": false, + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" }, "infer-owner": { "version": "1.0.4", - "bundled": true + "resolved": false, + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==" }, "inflight": { "version": "1.0.6", - "bundled": true, + "resolved": false, + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "requires": { "once": "^1.3.0", "wrappy": "1" @@ -15279,15 +15427,18 @@ }, "inherits": { "version": "2.0.4", - "bundled": true + "resolved": false, + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "1.3.8", - "bundled": true + "resolved": false, + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "init-package-json": { "version": "1.10.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-zKSiXKhQveNteyhcj1CoOP8tqp1QuxPIPBl8Bid99DGLFqA1p87M6lNgfjJHSBoWJJlidGOv5rWjyYKEB3g2Jw==", "requires": { "glob": "^7.1.1", "npm-package-arg": "^4.0.0 || ^5.0.0 || ^6.0.0", @@ -15301,50 +15452,59 @@ }, "ip": { "version": "1.1.5", - "bundled": true + "resolved": false, + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" }, "ip-regex": { "version": "2.1.0", - "bundled": true + "resolved": false, + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=" }, "is-callable": { "version": "1.1.4", - "bundled": true + "resolved": false, + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==" }, "is-ci": { "version": "1.2.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", "requires": { "ci-info": "^1.5.0" }, "dependencies": { "ci-info": { "version": "1.6.0", - "bundled": true + "resolved": false, + "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==" } } }, "is-cidr": { "version": "3.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/is-cidr/-/is-cidr-3.1.1.tgz", + "integrity": "sha512-Gx+oErgq1j2jAKCR2Kbq0b3wbH0vQKqZ0wOlHxm0o56nq51Cs/DZA8oz9dMDhbHyHEGgJ86eTeVudtgMMOx3Mw==", "requires": { "cidr-regex": "^2.0.10" } }, "is-date-object": { "version": "1.0.1", - "bundled": true + "resolved": false, + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" }, "is-fullwidth-code-point": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "requires": { "number-is-nan": "^1.0.0" } }, "is-installed-globally": { "version": "0.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", "requires": { "global-dirs": "^0.1.0", "is-path-inside": "^1.0.0" @@ -15352,88 +15512,107 @@ }, "is-npm": { "version": "1.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=" }, "is-obj": { "version": "1.0.1", - "bundled": true + "resolved": false, + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" }, "is-path-inside": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", "requires": { "path-is-inside": "^1.0.1" } }, "is-redirect": { "version": "1.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=" }, "is-regex": { "version": "1.0.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", "requires": { "has": "^1.0.1" } }, "is-retry-allowed": { "version": "1.2.0", - "bundled": true + "resolved": false, + "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==" }, "is-stream": { "version": "1.1.0", - "bundled": true + "resolved": false, + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" }, "is-symbol": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", "requires": { "has-symbols": "^1.0.0" } }, "is-typedarray": { "version": "1.0.0", - "bundled": true + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" }, "isarray": { "version": "1.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isstream": { "version": "0.1.2", - "bundled": true + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" }, "jsbn": { "version": "0.1.1", - "bundled": true + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" }, "json-parse-better-errors": { "version": "1.0.2", - "bundled": true + "resolved": false, + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" }, "json-parse-even-better-errors": { "version": "2.3.1", - "bundled": true + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, "json-schema": { "version": "0.4.0", - "bundled": true + "resolved": false, + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" }, "json-stringify-safe": { "version": "5.0.1", - "bundled": true + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" }, "jsonparse": { "version": "1.3.1", - "bundled": true + "resolved": false, + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" }, "jsprim": { "version": "1.4.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", @@ -15443,18 +15622,21 @@ }, "latest-version": { "version": "3.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", "requires": { "package-json": "^4.0.0" } }, "lazy-property": { "version": "1.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-hN3Es3Bnm6i9TNz6TAa0PVcREUc=" }, "libcipm": { "version": "4.0.8", - "bundled": true, + "resolved": false, + "integrity": "sha512-IN3hh2yDJQtZZ5paSV4fbvJg4aHxCCg5tcZID/dSVlTuUiWktsgaldVljJv6Z5OUlYspx6xQkbR0efNodnIrOA==", "requires": { "bin-links": "^1.1.2", "bluebird": "^3.5.1", @@ -15475,7 +15657,8 @@ }, "libnpm": { "version": "3.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-d7jU5ZcMiTfBqTUJVZ3xid44fE5ERBm9vBnmhp2ECD2Ls+FNXWxHSkO7gtvrnbLO78gwPdNPz1HpsF3W4rjkBQ==", "requires": { "bin-links": "^1.1.2", "bluebird": "^3.5.3", @@ -15501,7 +15684,8 @@ }, "libnpmaccess": { "version": "3.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-01512AK7MqByrI2mfC7h5j8N9V4I7MHJuk9buo8Gv+5QgThpOgpjB7sQBDDkeZqRteFb1QM/6YNdHfG7cDvfAQ==", "requires": { "aproba": "^2.0.0", "get-stream": "^4.0.0", @@ -15511,7 +15695,8 @@ }, "libnpmconfig": { "version": "1.2.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-9esX8rTQAHqarx6qeZqmGQKBNZR5OIbl/Ayr0qQDy3oXja2iFVQQI81R6GZ2a02bSNZ9p3YOGX1O6HHCb1X7kA==", "requires": { "figgy-pudding": "^3.5.1", "find-up": "^3.0.0", @@ -15520,14 +15705,16 @@ "dependencies": { "find-up": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "requires": { "locate-path": "^3.0.0" } }, "locate-path": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "requires": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" @@ -15535,27 +15722,31 @@ }, "p-limit": { "version": "2.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", "requires": { "p-try": "^2.0.0" } }, "p-locate": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "requires": { "p-limit": "^2.0.0" } }, "p-try": { "version": "2.2.0", - "bundled": true + "resolved": false, + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" } } }, "libnpmhook": { "version": "5.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-UdNLMuefVZra/wbnBXECZPefHMGsVDTq5zaM/LgKNE9Keyl5YXQTnGAzEo+nFOpdRqTWI9LYi4ApqF9uVCCtuA==", "requires": { "aproba": "^2.0.0", "figgy-pudding": "^3.4.1", @@ -15565,7 +15756,8 @@ }, "libnpmorg": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-0sRUXLh+PLBgZmARvthhYXQAWn0fOsa6T5l3JSe2n9vKG/lCVK4nuG7pDsa7uMq+uTt2epdPK+a2g6btcY11Ww==", "requires": { "aproba": "^2.0.0", "figgy-pudding": "^3.4.1", @@ -15575,7 +15767,8 @@ }, "libnpmpublish": { "version": "1.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-2yIwaXrhTTcF7bkJKIKmaCV9wZOALf/gsTDxVSu/Gu/6wiG3fA8ce8YKstiWKTxSFNC0R7isPUb6tXTVFZHt2g==", "requires": { "aproba": "^2.0.0", "figgy-pudding": "^3.5.1", @@ -15590,7 +15783,8 @@ }, "libnpmsearch": { "version": "2.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-VTBbV55Q6fRzTdzziYCr64+f8AopQ1YZ+BdPOv16UegIEaE8C0Kch01wo4s3kRTFV64P121WZJwgmBwrq68zYg==", "requires": { "figgy-pudding": "^3.5.1", "get-stream": "^4.0.0", @@ -15599,7 +15793,8 @@ }, "libnpmteam": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-p420vM28Us04NAcg1rzgGW63LMM6rwe+6rtZpfDxCcXxM0zUTLl7nPFEnRF3JfFBF5skF/yuZDUthTsHgde8QA==", "requires": { "aproba": "^2.0.0", "figgy-pudding": "^3.4.1", @@ -15609,7 +15804,8 @@ }, "libnpx": { "version": "10.2.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-BPc0D1cOjBeS8VIBKUu5F80s6njm0wbVt7CsGMrIcJ+SI7pi7V0uVPGpEMH9H5L8csOcclTxAXFE2VAsJXUhfA==", "requires": { "dotenv": "^5.0.1", "npm-package-arg": "^6.0.0", @@ -15623,7 +15819,8 @@ }, "lock-verify": { "version": "2.2.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/lock-verify/-/lock-verify-2.2.2.tgz", + "integrity": "sha512-2CUNtr1ZSVKJHcYP8uEzafmmuyauCB5zZimj8TvQd/Lflt9kXVZs+8S+EbAzZLaVUDn8CYGmeC3DFGdYfnCzeQ==", "requires": { "@iarna/cli": "^2.1.0", "npm-package-arg": "^6.1.0", @@ -15632,18 +15829,21 @@ }, "lockfile": { "version": "1.0.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA==", "requires": { "signal-exit": "^3.0.2" } }, "lodash._baseindexof": { "version": "3.1.0", - "bundled": true + "resolved": false, + "integrity": "sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw=" }, "lodash._baseuniq": { "version": "4.6.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-DrtE5FaBSveQXGIS+iybLVG4Qeg=", "requires": { "lodash._createset": "~4.0.0", "lodash._root": "~3.0.0" @@ -15651,72 +15851,87 @@ }, "lodash._bindcallback": { "version": "3.0.1", - "bundled": true + "resolved": false, + "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=" }, "lodash._cacheindexof": { "version": "3.0.2", - "bundled": true + "resolved": false, + "integrity": "sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI=" }, "lodash._createcache": { "version": "3.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-VtagZAF2JeeevKa4AY4XRAvc8JM=", "requires": { "lodash._getnative": "^3.0.0" } }, "lodash._createset": { "version": "4.0.3", - "bundled": true + "resolved": false, + "integrity": "sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY=" }, "lodash._getnative": { "version": "3.9.1", - "bundled": true + "resolved": false, + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" }, "lodash._root": { "version": "3.0.1", - "bundled": true + "resolved": false, + "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=" }, "lodash.clonedeep": { "version": "4.5.0", - "bundled": true + "resolved": false, + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" }, "lodash.restparam": { "version": "3.6.1", - "bundled": true + "resolved": false, + "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=" }, "lodash.union": { "version": "4.6.0", - "bundled": true + "resolved": false, + "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=" }, "lodash.uniq": { "version": "4.5.0", - "bundled": true + "resolved": false, + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" }, "lodash.without": { "version": "4.4.0", - "bundled": true + "resolved": false, + "integrity": "sha1-PNRXSgC2e643OpS3SHcmQFB7eqw=" }, "lowercase-keys": { "version": "1.0.1", - "bundled": true + "resolved": false, + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" }, "lru-cache": { "version": "5.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "requires": { "yallist": "^3.0.2" } }, "make-dir": { "version": "1.3.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", "requires": { "pify": "^3.0.0" } }, "make-fetch-happen": { "version": "5.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-07JHC0r1ykIoruKO8ifMXu+xEU8qOXDFETylktdug6vJDACnP+HKevOu3PXyNPzFyTSlz8vrBYlBO1JZRe8Cag==", "requires": { "agentkeepalive": "^3.4.1", "cacache": "^12.0.0", @@ -15733,40 +15948,47 @@ }, "meant": { "version": "1.0.3", - "bundled": true + "resolved": "https://registry.npmjs.org/meant/-/meant-1.0.3.tgz", + "integrity": "sha512-88ZRGcNxAq4EH38cQ4D85PM57pikCwS8Z99EWHODxN7KBY+UuPiqzRTtZzS8KTXO/ywSWbdjjJST2Hly/EQxLw==" }, "mime-db": { "version": "1.35.0", - "bundled": true + "resolved": false, + "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==" }, "mime-types": { "version": "2.1.19", - "bundled": true, + "resolved": false, + "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==", "requires": { "mime-db": "~1.35.0" } }, "minimatch": { "version": "3.1.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "1.2.6", - "bundled": true + "resolved": false, + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, "minizlib": { "version": "1.3.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", "requires": { "minipass": "^2.9.0" }, "dependencies": { "minipass": { "version": "2.9.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -15776,7 +15998,8 @@ }, "mississippi": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", "requires": { "concat-stream": "^1.5.0", "duplexify": "^3.4.2", @@ -15792,14 +16015,16 @@ }, "mkdirp": { "version": "0.5.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "requires": { "minimist": "^1.2.6" } }, "move-concurrently": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", "requires": { "aproba": "^1.1.1", "copy-concurrently": "^1.0.0", @@ -15811,21 +16036,25 @@ "dependencies": { "aproba": { "version": "1.2.0", - "bundled": true + "resolved": false, + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" } } }, "ms": { "version": "2.1.1", - "bundled": true + "resolved": false, + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" }, "mute-stream": { "version": "0.0.7", - "bundled": true + "resolved": false, + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=" }, "node-fetch-npm": { "version": "2.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-nJIxm1QmAj4v3nfCvEeCrYSoVwXyxLnaPBK5W1W5DGEJwjlKuC2VEUycGw5oxk+4zZahRrB84PUJJgEmhFTDFw==", "requires": { "encoding": "^0.1.11", "json-parse-better-errors": "^1.0.0", @@ -15834,7 +16063,8 @@ }, "node-gyp": { "version": "5.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-5.1.1.tgz", + "integrity": "sha512-WH0WKGi+a4i4DUt2mHnvocex/xPLp9pYt5R6M2JdFB7pJ7Z34hveZ4nDTGTiLXCkitA9T8HFZjhinBCiVHYcWw==", "requires": { "env-paths": "^2.2.0", "glob": "^7.1.4", @@ -15851,7 +16081,8 @@ }, "nopt": { "version": "4.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", "requires": { "abbrev": "1", "osenv": "^0.1.4" @@ -15859,7 +16090,8 @@ }, "normalize-package-data": { "version": "2.5.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "requires": { "hosted-git-info": "^2.1.4", "resolve": "^1.10.0", @@ -15869,7 +16101,8 @@ "dependencies": { "resolve": { "version": "1.10.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", "requires": { "path-parse": "^1.0.6" } @@ -15878,7 +16111,8 @@ }, "npm-audit-report": { "version": "1.3.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-8nH/JjsFfAWMvn474HB9mpmMjrnKb1Hx/oTAdjv4PT9iZBvBxiZ+wtDUapHCJwLqYGQVPaAfs+vL5+5k9QndXw==", "requires": { "cli-table3": "^0.5.0", "console-control-strings": "^1.1.0" @@ -15886,25 +16120,29 @@ }, "npm-bundled": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", "requires": { "npm-normalize-package-bin": "^1.0.1" } }, "npm-cache-filename": { "version": "1.0.2", - "bundled": true + "resolved": false, + "integrity": "sha1-3tMGxbC/yHCp6fr4I7xfKD4FrhE=" }, "npm-install-checks": { "version": "3.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-E4kzkyZDIWoin6uT5howP8VDvkM+E8IQDcHAycaAxMbwkqhIg5eEYALnXOl3Hq9MrkdQB/2/g1xwBINXdKSRkg==", "requires": { "semver": "^2.3.0 || 3.x || 4 || 5" } }, "npm-lifecycle": { "version": "3.1.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-lDLVkjfZmvmfvpvBzA4vzee9cn+Me4orq0QF8glbswJVEbIcSNWib7qGOffolysc3teCqbbPZZkzbr3GQZTL1g==", "requires": { "byline": "^5.0.0", "graceful-fs": "^4.1.15", @@ -15918,15 +16156,18 @@ }, "npm-logical-tree": { "version": "1.2.1", - "bundled": true + "resolved": false, + "integrity": "sha512-AJI/qxDB2PWI4LG1CYN579AY1vCiNyWfkiquCsJWqntRu/WwimVrC8yXeILBFHDwxfOejxewlmnvW9XXjMlYIg==" }, "npm-normalize-package-bin": { "version": "1.0.1", - "bundled": true + "resolved": false, + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==" }, "npm-package-arg": { "version": "6.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", "requires": { "hosted-git-info": "^2.7.1", "osenv": "^0.1.5", @@ -15936,7 +16177,8 @@ }, "npm-packlist": { "version": "1.4.8", - "bundled": true, + "resolved": false, + "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", "requires": { "ignore-walk": "^3.0.1", "npm-bundled": "^1.0.1", @@ -15945,7 +16187,8 @@ }, "npm-pick-manifest": { "version": "3.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-wNprTNg+X5nf+tDi+hbjdHhM4bX+mKqv6XmPh7B5eG+QY9VARfQPfCEH013H5GqfNj6ee8Ij2fg8yk0mzps1Vw==", "requires": { "figgy-pudding": "^3.5.1", "npm-package-arg": "^6.0.0", @@ -15954,7 +16197,8 @@ }, "npm-profile": { "version": "4.0.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-Ta8xq8TLMpqssF0H60BXS1A90iMoM6GeKwsmravJ6wYjWwSzcYBTdyWa3DZCYqPutacBMEm7cxiOkiIeCUAHDQ==", "requires": { "aproba": "^1.1.2 || 2", "figgy-pudding": "^3.4.1", @@ -15963,7 +16207,8 @@ }, "npm-registry-fetch": { "version": "4.0.7", - "bundled": true, + "resolved": false, + "integrity": "sha512-cny9v0+Mq6Tjz+e0erFAB+RYJ/AVGzkjnISiobqP8OWj9c9FLoZZu8/SPSKJWE17F1tk4018wfjV+ZbIbqC7fQ==", "requires": { "JSONStream": "^1.3.4", "bluebird": "^3.5.1", @@ -15976,24 +16221,28 @@ "dependencies": { "safe-buffer": { "version": "5.2.1", - "bundled": true + "resolved": false, + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" } } }, "npm-run-path": { "version": "2.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", "requires": { "path-key": "^2.0.0" } }, "npm-user-validate": { "version": "1.0.1", - "bundled": true + "resolved": false, + "integrity": "sha512-uQwcd/tY+h1jnEaze6cdX/LrhWhoBxfSknxentoqmIuStxUExxjWd3ULMLFPiFUrZKbOVMowH6Jq2FRWfmhcEw==" }, "npmlog": { "version": "4.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "requires": { "are-we-there-yet": "~1.1.2", "console-control-strings": "~1.1.0", @@ -16003,23 +16252,28 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true + "resolved": false, + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "oauth-sign": { "version": "0.9.0", - "bundled": true + "resolved": false, + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" }, "object-assign": { "version": "4.1.1", - "bundled": true + "resolved": false, + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-keys": { "version": "1.0.12", - "bundled": true + "resolved": false, + "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==" }, "object.getownpropertydescriptors": { "version": "2.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", "requires": { "define-properties": "^1.1.2", "es-abstract": "^1.5.1" @@ -16027,26 +16281,31 @@ }, "once": { "version": "1.4.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { "wrappy": "1" } }, "opener": { "version": "1.5.2", - "bundled": true + "resolved": false, + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==" }, "os-homedir": { "version": "1.0.2", - "bundled": true + "resolved": false, + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" }, "os-tmpdir": { "version": "1.0.2", - "bundled": true + "resolved": false, + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, "osenv": { "version": "0.1.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "requires": { "os-homedir": "^1.0.0", "os-tmpdir": "^1.0.0" @@ -16054,11 +16313,13 @@ }, "p-finally": { "version": "1.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" }, "package-json": { "version": "4.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", "requires": { "got": "^6.7.1", "registry-auth-token": "^3.0.1", @@ -16068,7 +16329,8 @@ }, "pacote": { "version": "9.5.12", - "bundled": true, + "resolved": false, + "integrity": "sha512-BUIj/4kKbwWg4RtnBncXPJd15piFSVNpTzY0rysSr3VnMowTYgkGKcaHrbReepAkjTr8lH2CVWRi58Spg2CicQ==", "requires": { "bluebird": "^3.5.3", "cacache": "^12.0.2", @@ -16104,7 +16366,8 @@ "dependencies": { "minipass": { "version": "2.9.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -16114,7 +16377,8 @@ }, "parallel-transform": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", "requires": { "cyclist": "~0.2.2", "inherits": "^2.0.3", @@ -16123,7 +16387,8 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -16136,20 +16401,23 @@ "dependencies": { "safe-buffer": { "version": "5.1.2", - "bundled": true + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } }, "string_decoder": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" }, "dependencies": { "safe-buffer": { "version": "5.1.2", - "bundled": true + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } } @@ -16157,47 +16425,58 @@ }, "path-exists": { "version": "3.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" }, "path-is-absolute": { "version": "1.0.1", - "bundled": true + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" }, "path-is-inside": { "version": "1.0.2", - "bundled": true + "resolved": false, + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" }, "path-key": { "version": "2.0.1", - "bundled": true + "resolved": false, + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" }, "path-parse": { "version": "1.0.7", - "bundled": true + "resolved": false, + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "performance-now": { "version": "2.1.0", - "bundled": true + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" }, "pify": { "version": "3.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" }, "prepend-http": { "version": "1.0.4", - "bundled": true + "resolved": false, + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" }, "process-nextick-args": { "version": "2.0.0", - "bundled": true + "resolved": false, + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, "promise-inflight": { "version": "1.0.1", - "bundled": true + "resolved": false, + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=" }, "promise-retry": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-ZznpaOMFHaIM5kl/srUPaRHfPW0=", "requires": { "err-code": "^1.0.0", "retry": "^0.10.0" @@ -16205,43 +16484,51 @@ "dependencies": { "retry": { "version": "0.10.1", - "bundled": true + "resolved": false, + "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=" } } }, "promzard": { "version": "0.3.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-JqXW7ox97kyxIggwWs+5O6OCqe4=", "requires": { "read": "1" } }, "proto-list": { "version": "1.2.4", - "bundled": true + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==" }, "protoduck": { "version": "5.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-WxoCeDCoCBY55BMvj4cAEjdVUFGRWed9ZxPlqTKYyw1nDDTQ4pqmnIMAGfJlg7Dx35uB/M+PHJPTmGOvaCaPTg==", "requires": { "genfun": "^5.0.0" } }, "prr": { "version": "1.0.1", - "bundled": true + "resolved": false, + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" }, "pseudomap": { "version": "1.0.2", - "bundled": true + "resolved": false, + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, "psl": { "version": "1.9.0", - "bundled": true + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" }, "pump": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -16249,7 +16536,8 @@ }, "pumpify": { "version": "1.5.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", "requires": { "duplexify": "^3.6.0", "inherits": "^2.0.3", @@ -16258,7 +16546,8 @@ "dependencies": { "pump": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -16268,15 +16557,18 @@ }, "qrcode-terminal": { "version": "0.12.0", - "bundled": true + "resolved": false, + "integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==" }, "qs": { "version": "6.5.3", - "bundled": true + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==" }, "query-string": { "version": "6.14.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.14.1.tgz", + "integrity": "sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw==", "requires": { "decode-uri-component": "^0.2.0", "filter-obj": "^1.1.0", @@ -16286,11 +16578,13 @@ }, "qw": { "version": "1.0.2", - "bundled": true + "resolved": "https://registry.npmjs.org/qw/-/qw-1.0.2.tgz", + "integrity": "sha512-1PhZ/iLKwlVNq45dnerTMKFjMof49uqli7/0QsvPNbX5OJ3IZ8msa9lUpvPheVdP+IYYPrf6cOaVil7S35joVA==" }, "rc": { "version": "1.2.8", - "bundled": true, + "resolved": false, + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "requires": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -16300,21 +16594,24 @@ }, "read": { "version": "1.0.7", - "bundled": true, + "resolved": false, + "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", "requires": { "mute-stream": "~0.0.4" } }, "read-cmd-shim": { "version": "1.0.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-v5yCqQ/7okKoZZkBQUAfTsQ3sVJtXdNfbPnI5cceppoxEVLYA3k+VtV2omkeo8MS94JCy4fSiUwlRBAwCVRPUA==", "requires": { "graceful-fs": "^4.1.2" } }, "read-installed": { "version": "4.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-/5uLZ/GH0eTCm5/rMfayI6zRkGc=", "requires": { "debuglog": "^1.0.1", "graceful-fs": "^4.1.2", @@ -16327,7 +16624,8 @@ }, "read-package-json": { "version": "2.1.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.2.tgz", + "integrity": "sha512-D1KmuLQr6ZSJS0tW8hf3WGpRlwszJOXZ3E8Yd/DNRaM5d+1wVRZdHlpGBLAuovjr28LbWvjpWkBHMxpRGGjzNA==", "requires": { "glob": "^7.1.1", "json-parse-even-better-errors": "^2.3.0", @@ -16337,7 +16635,8 @@ }, "read-package-tree": { "version": "5.3.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-mLUDsD5JVtlZxjSlPPx1RETkNjjvQYuweKwNVt1Sn8kP5Jh44pvYuUHCp6xSVDZWbNxVxG5lyZJ921aJH61sTw==", "requires": { "read-package-json": "^2.0.0", "readdir-scoped-modules": "^1.0.0", @@ -16346,7 +16645,8 @@ }, "readable-stream": { "version": "3.6.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -16355,7 +16655,8 @@ }, "readdir-scoped-modules": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==", "requires": { "debuglog": "^1.0.1", "dezalgo": "^1.0.0", @@ -16365,7 +16666,8 @@ }, "registry-auth-token": { "version": "3.4.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", "requires": { "rc": "^1.1.6", "safe-buffer": "^5.0.1" @@ -16373,14 +16675,16 @@ }, "registry-url": { "version": "3.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", "requires": { "rc": "^1.0.1" } }, "request": { "version": "2.88.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", "requires": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -16406,96 +16710,115 @@ }, "require-directory": { "version": "2.1.1", - "bundled": true + "resolved": false, + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" }, "require-main-filename": { "version": "2.0.0", - "bundled": true + "resolved": false, + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" }, "resolve-from": { "version": "4.0.0", - "bundled": true + "resolved": false, + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" }, "retry": { "version": "0.12.0", - "bundled": true + "resolved": false, + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=" }, "rimraf": { "version": "2.7.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "requires": { "glob": "^7.1.3" } }, "run-queue": { "version": "1.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", "requires": { "aproba": "^1.1.1" }, "dependencies": { "aproba": { "version": "1.2.0", - "bundled": true + "resolved": false, + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" } } }, "safe-buffer": { "version": "5.2.1", - "bundled": true + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, "safer-buffer": { "version": "2.1.2", - "bundled": true + "resolved": false, + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "semver": { "version": "5.7.1", - "bundled": true + "resolved": false, + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, "semver-diff": { "version": "2.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", "requires": { "semver": "^5.0.3" } }, "set-blocking": { "version": "2.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "sha": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-DOYnM37cNsLNSGIG/zZWch5CKIRNoLdYUQTQlcgkRkoYIUwDYjqDyye16YcDZg/OPdcbUgTKMjc4SY6TB7ZAPw==", "requires": { "graceful-fs": "^4.1.2" } }, "shebang-command": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "requires": { "shebang-regex": "^1.0.0" } }, "shebang-regex": { "version": "1.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" }, "signal-exit": { "version": "3.0.2", - "bundled": true + "resolved": false, + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "slide": { "version": "1.1.6", - "bundled": true + "resolved": false, + "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=" }, "smart-buffer": { "version": "4.1.0", - "bundled": true + "resolved": false, + "integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==" }, "socks": { "version": "2.3.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA==", "requires": { "ip": "1.1.5", "smart-buffer": "^4.1.0" @@ -16503,7 +16826,8 @@ }, "socks-proxy-agent": { "version": "4.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==", "requires": { "agent-base": "~4.2.1", "socks": "~2.3.2" @@ -16511,7 +16835,8 @@ "dependencies": { "agent-base": { "version": "4.2.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", "requires": { "es6-promisify": "^5.0.0" } @@ -16520,11 +16845,13 @@ }, "sorted-object": { "version": "2.0.1", - "bundled": true + "resolved": false, + "integrity": "sha1-fWMfS9OnmKJK8d/8+/6DM3pd9fw=" }, "sorted-union-stream": { "version": "2.1.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-x3lMfgd4gAUv9xqNSi27Sppjisc=", "requires": { "from2": "^1.3.0", "stream-iterate": "^1.1.0" @@ -16532,7 +16859,8 @@ "dependencies": { "from2": { "version": "1.3.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-iEE7qqX5pZfP3pIh2GmGzTwGHf0=", "requires": { "inherits": "~2.0.1", "readable-stream": "~1.1.10" @@ -16540,11 +16868,13 @@ }, "isarray": { "version": "0.0.1", - "bundled": true + "resolved": false, + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" }, "readable-stream": { "version": "1.1.14", - "bundled": true, + "resolved": false, + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", @@ -16554,13 +16884,15 @@ }, "string_decoder": { "version": "0.10.31", - "bundled": true + "resolved": false, + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" } } }, "spdx-correct": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", "requires": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" @@ -16568,11 +16900,13 @@ }, "spdx-exceptions": { "version": "2.1.0", - "bundled": true + "resolved": false, + "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==" }, "spdx-expression-parse": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", "requires": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" @@ -16580,15 +16914,18 @@ }, "spdx-license-ids": { "version": "3.0.5", - "bundled": true + "resolved": false, + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==" }, "split-on-first": { "version": "1.1.0", - "bundled": true + "resolved": false, + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==" }, "sshpk": { "version": "1.17.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", + "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -16603,14 +16940,16 @@ }, "ssri": { "version": "6.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", "requires": { "figgy-pudding": "^3.5.1" } }, "stream-each": { "version": "1.2.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-mc1dbFhGBxvTM3bIWmAAINbqiuAk9TATcfIQC8P+/+HJefgaiTlMn2dHvkX8qlI12KeYKSQ1Ua9RrIqrn1VPoA==", "requires": { "end-of-stream": "^1.1.0", "stream-shift": "^1.0.0" @@ -16618,7 +16957,8 @@ }, "stream-iterate": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-K9fHcpbBcCpGSIuK1B95hl7s1OE=", "requires": { "readable-stream": "^2.1.5", "stream-shift": "^1.0.0" @@ -16626,7 +16966,8 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -16639,20 +16980,23 @@ "dependencies": { "safe-buffer": { "version": "5.1.2", - "bundled": true + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } }, "string_decoder": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" }, "dependencies": { "safe-buffer": { "version": "5.1.2", - "bundled": true + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } } @@ -16660,15 +17004,18 @@ }, "stream-shift": { "version": "1.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=" }, "strict-uri-encode": { "version": "2.0.0", - "bundled": true + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==" }, "string-width": { "version": "2.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "requires": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" @@ -16676,15 +17023,18 @@ "dependencies": { "ansi-regex": { "version": "3.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" }, "is-fullwidth-code-point": { "version": "2.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, "strip-ansi": { "version": "4.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "requires": { "ansi-regex": "^3.0.0" } @@ -16693,46 +17043,54 @@ }, "string_decoder": { "version": "1.3.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "requires": { "safe-buffer": "~5.2.0" }, "dependencies": { "safe-buffer": { "version": "5.2.0", - "bundled": true + "resolved": false, + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" } } }, "stringify-package": { "version": "1.0.1", - "bundled": true + "resolved": false, + "integrity": "sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==" }, "strip-ansi": { "version": "3.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { "ansi-regex": "^2.0.0" } }, "strip-eof": { "version": "1.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" }, "strip-json-comments": { "version": "2.0.1", - "bundled": true + "resolved": false, + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" }, "supports-color": { "version": "5.4.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "requires": { "has-flag": "^3.0.0" } }, "tar": { "version": "4.4.19", - "bundled": true, + "resolved": false, + "integrity": "sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==", "requires": { "chownr": "^1.1.4", "fs-minipass": "^1.2.7", @@ -16745,7 +17103,8 @@ "dependencies": { "minipass": { "version": "2.9.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -16753,32 +17112,38 @@ }, "safe-buffer": { "version": "5.2.1", - "bundled": true + "resolved": false, + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, "yallist": { "version": "3.1.1", - "bundled": true + "resolved": false, + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" } } }, "term-size": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", "requires": { "execa": "^0.7.0" } }, "text-table": { "version": "0.2.0", - "bundled": true + "resolved": false, + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=" }, "through": { "version": "2.3.8", - "bundled": true + "resolved": false, + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, "through2": { "version": "2.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", "requires": { "readable-stream": "^2.1.5", "xtend": "~4.0.1" @@ -16786,7 +17151,8 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -16799,20 +17165,23 @@ "dependencies": { "safe-buffer": { "version": "5.1.2", - "bundled": true + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } }, "string_decoder": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" }, "dependencies": { "safe-buffer": { "version": "5.1.2", - "bundled": true + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } } @@ -16820,15 +17189,18 @@ }, "timed-out": { "version": "4.0.1", - "bundled": true + "resolved": false, + "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" }, "tiny-relative-date": { "version": "1.3.0", - "bundled": true + "resolved": false, + "integrity": "sha512-MOQHpzllWxDCHHaDno30hhLfbouoYlOI8YlMNtvKe1zXbjEVhbcEovQxvZrPvtiYW630GQDoMMarCnjfyfHA+A==" }, "tough-cookie": { "version": "2.5.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", "requires": { "psl": "^1.1.28", "punycode": "^2.1.1" @@ -16836,65 +17208,77 @@ "dependencies": { "punycode": { "version": "2.1.1", - "bundled": true + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" } } }, "tunnel-agent": { "version": "0.6.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "requires": { "safe-buffer": "^5.0.1" } }, "tweetnacl": { "version": "0.14.5", - "bundled": true + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" }, "typedarray": { "version": "0.0.6", - "bundled": true + "resolved": false, + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, "uid-number": { "version": "0.0.6", - "bundled": true + "resolved": false, + "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=" }, "umask": { "version": "1.1.0", - "bundled": true + "resolved": false, + "integrity": "sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0=" }, "unique-filename": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", "requires": { "unique-slug": "^2.0.0" } }, "unique-slug": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-22Z258fMBimHj/GWCXx4hVrp9Ks=", "requires": { "imurmurhash": "^0.1.4" } }, "unique-string": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", "requires": { "crypto-random-string": "^1.0.0" } }, "unpipe": { "version": "1.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, "unzip-response": { "version": "2.0.1", - "bundled": true + "resolved": false, + "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=" }, "update-notifier": { "version": "2.5.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==", "requires": { "boxen": "^1.2.1", "chalk": "^2.0.1", @@ -16910,46 +17294,54 @@ }, "uri-js": { "version": "4.4.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "requires": { "punycode": "^2.1.0" }, "dependencies": { "punycode": { "version": "2.1.1", - "bundled": true + "resolved": false, + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" } } }, "url-parse-lax": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", "requires": { "prepend-http": "^1.0.1" } }, "util-deprecate": { "version": "1.0.2", - "bundled": true + "resolved": false, + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "util-extend": { "version": "1.0.3", - "bundled": true + "resolved": false, + "integrity": "sha1-p8IW0mdUUWljeztu3GypEZ4v+T8=" }, "util-promisify": { "version": "2.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-PCI2R2xNMsX/PEcAKt18E7moKlM=", "requires": { "object.getownpropertydescriptors": "^2.0.3" } }, "uuid": { "version": "3.4.0", - "bundled": true + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" }, "validate-npm-package-license": { "version": "3.0.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "requires": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" @@ -16957,14 +17349,16 @@ }, "validate-npm-package-name": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", "requires": { "builtins": "^1.0.3" } }, "verror": { "version": "1.10.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", "requires": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", @@ -16973,32 +17367,37 @@ }, "wcwidth": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", "requires": { "defaults": "^1.0.3" } }, "which": { "version": "1.3.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "requires": { "isexe": "^2.0.0" } }, "which-module": { "version": "2.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" }, "wide-align": { "version": "1.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", "requires": { "string-width": "^1.0.2" }, "dependencies": { "string-width": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -17009,21 +17408,24 @@ }, "widest-line": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", "requires": { "string-width": "^2.1.1" } }, "worker-farm": { "version": "1.7.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", "requires": { "errno": "~0.1.7" } }, "wrap-ansi": { "version": "5.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", "requires": { "ansi-styles": "^3.2.0", "string-width": "^3.0.0", @@ -17032,15 +17434,18 @@ "dependencies": { "ansi-regex": { "version": "4.1.1", - "bundled": true + "resolved": false, + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==" }, "is-fullwidth-code-point": { "version": "2.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, "string-width": { "version": "3.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "requires": { "emoji-regex": "^7.0.1", "is-fullwidth-code-point": "^2.0.0", @@ -17049,7 +17454,8 @@ }, "strip-ansi": { "version": "5.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "requires": { "ansi-regex": "^4.1.0" } @@ -17058,11 +17464,13 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true + "resolved": false, + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write-file-atomic": { "version": "2.4.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", "requires": { "graceful-fs": "^4.1.11", "imurmurhash": "^0.1.4", @@ -17071,23 +17479,28 @@ }, "xdg-basedir": { "version": "3.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=" }, "xtend": { "version": "4.0.1", - "bundled": true + "resolved": false, + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" }, "y18n": { "version": "4.0.1", - "bundled": true + "resolved": false, + "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==" }, "yallist": { "version": "3.0.3", - "bundled": true + "resolved": false, + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" }, "yargs": { "version": "14.2.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==", "requires": { "cliui": "^5.0.0", "decamelize": "^1.2.0", @@ -17104,22 +17517,26 @@ "dependencies": { "ansi-regex": { "version": "4.1.0", - "bundled": true + "resolved": false, + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" }, "find-up": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "requires": { "locate-path": "^3.0.0" } }, "is-fullwidth-code-point": { "version": "2.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, "locate-path": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "requires": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" @@ -17127,25 +17544,29 @@ }, "p-limit": { "version": "2.3.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "requires": { "p-try": "^2.0.0" } }, "p-locate": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "requires": { "p-limit": "^2.0.0" } }, "p-try": { "version": "2.2.0", - "bundled": true + "resolved": false, + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, "string-width": { "version": "3.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "requires": { "emoji-regex": "^7.0.1", "is-fullwidth-code-point": "^2.0.0", @@ -17154,7 +17575,8 @@ }, "strip-ansi": { "version": "5.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "requires": { "ansi-regex": "^4.1.0" } @@ -17163,7 +17585,8 @@ }, "yargs-parser": { "version": "15.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==", "requires": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" @@ -17171,7 +17594,8 @@ "dependencies": { "camelcase": { "version": "5.3.1", - "bundled": true + "resolved": false, + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" } } } @@ -17440,6 +17864,40 @@ "mimic-fn": "^2.1.0" } }, + "openai": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/openai/-/openai-3.2.1.tgz", + "integrity": "sha512-762C9BNlJPbjjlWZi4WYK9iM2tAVAv0uUp1UmI34vb0CN5T2mjB/qM6RYBmNKMh/dN9fC+bxqPwWJZUTWW052A==", + "requires": { + "axios": "^0.26.0", + "form-data": "^4.0.0" + }, + "dependencies": { + "axios": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", + "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "requires": { + "follow-redirects": "^1.14.8" + } + }, + "follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } + } + }, "opentype.js": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-1.3.4.tgz", @@ -18839,9 +19297,9 @@ } }, "react-chartjs-2": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-4.3.0.tgz", - "integrity": "sha512-zWnW3ReDlSC8PNiZKNqu8J/CHBOsVvx1AB4PicFbEAMUf/rpgO0/8ykRSPqS00kBUjP9d8lzfq1gFdLwYFWC8w==" + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-4.3.1.tgz", + "integrity": "sha512-5i3mjP6tU7QSn0jvb8I4hudTzHJqS8l00ORJnVwI2sYu0ihpj83Lv2YzfxunfxTZkscKvZu2F2w9LkwNBhj6xA==" }, "react-color": { "version": "2.19.3", @@ -19181,9 +19639,9 @@ "integrity": "sha512-F8TPx3z7/AcmRViySbYV3LpUWXFpHlGAmKmNcYMgPlS+h1eYFazRG3xYS8Z6e48hWY1EcCny/YNrwRNUrap8CQ==" }, "react-resize-detector": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-7.1.2.tgz", - "integrity": "sha512-zXnPJ2m8+6oq9Nn8zsep/orts9vQv3elrpA+R8XTcW7DVVUJ9vwDwMXaBtykAYjMnkCIaOoK9vObyR7ZgFNlOw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-8.1.0.tgz", + "integrity": "sha512-S7szxlaIuiy5UqLhLL1KY3aoyGHbZzsTpYal9eYMwCyKqoqoVLCmIgAgNyIM1FhnP2KyBygASJxdhejrzjMb+w==", "requires": { "lodash": "^4.17.21" } @@ -19224,11 +19682,11 @@ } }, "react-smooth": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-2.0.1.tgz", - "integrity": "sha512-Own9TA0GPPf3as4vSwFhDouVfXP15ie/wIHklhyKBH5AN6NFtdk0UpHBnonV11BtqDkAWlt40MOUc+5srmW7NA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-2.0.2.tgz", + "integrity": "sha512-pgqSp1q8rAGtF1bXQE0m3CHGLNfZZh5oA5o1tsPLXRHnKtkujMIJ8Ws5nO1mTySZf1c4vgwlEk+pHi3Ln6eYLw==", "requires": { - "fast-equals": "^2.0.0", + "fast-equals": "^4.0.3", "react-transition-group": "2.9.0" }, "dependencies": { @@ -19302,6 +19760,14 @@ } } }, + "react-typist": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/react-typist/-/react-typist-2.0.5.tgz", + "integrity": "sha512-iZCkeqeegO0TlkTMiH2JD1tvMtY9RrXkRylnAI6m8aCVAUUwNzoWTVF7CKLij6THeOMcUDCznLDDvNp55s+YZA==", + "requires": { + "prop-types": "^15.5.10" + } + }, "react-use-measure": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.1.tgz", @@ -19410,95 +19876,19 @@ "integrity": "sha1-xYDXfvLPyHUrEySYBg3JeTp6wBw=" }, "recharts": { - "version": "2.1.12", - "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.1.12.tgz", - "integrity": "sha512-dAzEuc9AjM+IF0A33QzEdBEUnyGKJcGUPa0MYm0vd38P3WouQjrj2egBrCNInE7ZcQwN+z3MoT7Rw03u8nP9HA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.5.0.tgz", + "integrity": "sha512-0EQYz3iA18r1Uq8VqGZ4dABW52AKBnio37kJgnztIqprELJXpOEsa0SzkqU1vjAhpCXCv52Dx1hiL9119xsqsQ==", "requires": { "classnames": "^2.2.5", - "d3-interpolate": "^2.0.0", - "d3-scale": "^3.0.0", - "d3-shape": "^2.0.0", "eventemitter3": "^4.0.1", "lodash": "^4.17.19", "react-is": "^16.10.2", - "react-resize-detector": "^7.1.2", - "react-smooth": "^2.0.1", + "react-resize-detector": "^8.0.4", + "react-smooth": "^2.0.2", "recharts-scale": "^0.4.4", - "reduce-css-calc": "^2.1.8" - }, - "dependencies": { - "d3-array": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", - "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", - "requires": { - "internmap": "^1.0.0" - } - }, - "d3-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-2.0.0.tgz", - "integrity": "sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ==" - }, - "d3-format": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-2.0.0.tgz", - "integrity": "sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA==" - }, - "d3-interpolate": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-2.0.1.tgz", - "integrity": "sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ==", - "requires": { - "d3-color": "1 - 2" - } - }, - "d3-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-2.0.0.tgz", - "integrity": "sha512-ZwZQxKhBnv9yHaiWd6ZU4x5BtCQ7pXszEV9CU6kRgwIQVQGLMv1oiL4M+MK/n79sYzsj+gcgpPQSctJUsLN7fA==" - }, - "d3-scale": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz", - "integrity": "sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ==", - "requires": { - "d3-array": "^2.3.0", - "d3-format": "1 - 2", - "d3-interpolate": "1.2.0 - 2", - "d3-time": "^2.1.1", - "d3-time-format": "2 - 3" - } - }, - "d3-shape": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-2.1.0.tgz", - "integrity": "sha512-PnjUqfM2PpskbSLTJvAzp2Wv4CZsnAgTfcVRTwW03QR3MkXF8Uo7B1y/lWkAsmbKwuecto++4NlsYcvYpXpTHA==", - "requires": { - "d3-path": "1 - 2" - } - }, - "d3-time": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz", - "integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==", - "requires": { - "d3-array": "2" - } - }, - "d3-time-format": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-3.0.0.tgz", - "integrity": "sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==", - "requires": { - "d3-time": "1 - 2" - } - }, - "internmap": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", - "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==" - } + "reduce-css-calc": "^2.1.8", + "victory-vendor": "^36.6.8" } }, "recharts-scale": { @@ -21778,7 +22168,7 @@ "to-array": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", - "integrity": "sha512-LhVdShQD/4Mk4zXNroIQZJC+Ap3zgLcDuwEdcmLv9CCO73NWockQDwyUnW/m8VX/EElfL6FcYx7EeutN4HJA6A==" + "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=" }, "to-fast-properties": { "version": "2.0.0", @@ -22150,7 +22540,7 @@ "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true } } @@ -22947,6 +23337,42 @@ "unist-util-stringify-position": "^3.0.0" } }, + "victory-vendor": { + "version": "36.6.8", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.6.8.tgz", + "integrity": "sha512-H3kyQ+2zgjMPvbPqAl7Vwm2FD5dU7/4bCTQakFQnpIsfDljeOMDojRsrmJfwh4oAlNnWhpAf+mbAoLh8u7dwyQ==", + "requires": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + }, + "dependencies": { + "@types/d3-scale": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.3.tgz", + "integrity": "sha512-PATBiMCpvHJSMtZAMEhc2WyL+hnzarKzI6wAHYjhsonjWJYGq5BXTzQjv4l8m2jO183/4wZ90rKvSeT7o72xNQ==", + "requires": { + "@types/d3-time": "*" + } + }, + "@types/d3-time": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz", + "integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==" + } + } + }, "void-elements": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", @@ -23371,7 +23797,7 @@ "resolve-cwd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", - "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "integrity": "sha512-ccu8zQTrzVr954472aUVPLEcB3YpKSYR3cg/3lo1okzobPBM+1INXBbBZlDbnI/hbEocnf8j0QVo43hQKrbchg==", "dev": true, "requires": { "resolve-from": "^3.0.0" @@ -23380,7 +23806,7 @@ "resolve-from": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", "dev": true }, "semver": { @@ -23932,7 +24358,7 @@ "yeast": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", - "integrity": "sha512-8HFIh676uyGYP6wP13R/j6OJ/1HwJ46snpvzE7aHAN3Ryqh2yX6Xox2B4CUmTwwOIzlG3Bs7ocsP5dZH/R1Qbg==" + "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" }, "yn": { "version": "3.1.1", diff --git a/package.json b/package.json index 0f9fc4895..70dcc1845 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "@types/react-select": "^3.1.2", "@types/react-table": "^6.8.9", "@types/react-transition-group": "^4.4.5", + "@types/react-typist": "^2.0.3", "@types/request": "^2.48.8", "@types/request-promise": "^4.1.48", "@types/rimraf": "^2.0.5", @@ -156,6 +157,7 @@ "@types/three": "^0.126.2", "@types/web": "0.0.53", "@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", @@ -188,7 +190,6 @@ "csv-parser": "^3.0.0", "csv-stringify": "^6.3.0", "d3": "^7.6.1", - "D": "^1.0.0", "depcheck": "^0.9.2", "equation-editor-react": "github:bobzel/equation-editor-react#useLocally", "exif": "^0.6.0", @@ -245,6 +246,7 @@ "nodemon": "^1.19.4", "normalize.css": "^8.0.1", "npm": "^6.14.18", + "openai": "^3.2.1", "p-limit": "^2.2.0", "passport": "^0.4.0", "passport-google-oauth20": "^2.0.0", @@ -293,6 +295,7 @@ "react-select": "^3.2.0", "react-table": "^6.11.5", "react-transition-group": "^4.4.2", + "react-typist": "^2.0.5", "readline": "^1.3.0", "recharts": "^2.1.12", "rehype-raw": "^6.1.1", diff --git a/src/Utils.ts b/src/Utils.ts index 0c7deaf5d..73de6d754 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -513,6 +513,9 @@ export function returnTrue() { return true; } +export function returnIgnore(): 'ignore' { + return 'ignore'; +} export function returnAlways(): 'always' { return 'always'; } diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index 8f79ebb03..ba64f993c 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -29,6 +29,13 @@ import { GestureOverlay } from './views/GestureOverlay'; export namespace DocServer { let _cache: { [id: string]: RefField | Promise<Opt<RefField>> } = {}; + export function QUERY_SERVER_CACHE(title: string) { + const foundDocId = Array.from(Object.keys(_cache)) + .filter(key => _cache[key] instanceof Doc) + .find(key => (_cache[key] as Doc).title === title); + + return foundDocId ? (_cache[foundDocId] as Doc) : undefined; + } export function UPDATE_SERVER_CACHE(print: boolean = false) { if (print) { const strings: string[] = []; diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts new file mode 100644 index 000000000..4b3960902 --- /dev/null +++ b/src/client/apis/gpt/GPT.ts @@ -0,0 +1,87 @@ +import { Configuration, OpenAIApi } from 'openai'; + +enum GPTCallType { + SUMMARY = 'summary', + COMPLETION = 'completion', + EDIT = 'edit', +} + +type GPTCallOpts = { + model: string; + maxTokens: number; + temp: number; + prompt: string; +}; + +const callTypeMap: { [type: string]: GPTCallOpts } = { + summary: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: 'Summarize this text briefly: ' }, + edit: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: 'Reword this: ' }, + completion: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: '' }, +}; + +/** + * Calls the OpenAI API. + * + * @param inputText Text to process + * @returns AI Output + */ +const gptAPICall = async (inputText: string, callType: GPTCallType) => { + if (callType === GPTCallType.SUMMARY) inputText += '.'; + const opts: GPTCallOpts = callTypeMap[callType]; + try { + const configuration = new Configuration({ + apiKey: process.env.OPENAI_KEY, + }); + const openai = new OpenAIApi(configuration); + const response = await openai.createCompletion({ + model: opts.model, + max_tokens: opts.maxTokens, + temperature: opts.temp, + prompt: `${opts.prompt}${inputText}`, + }); + console.log(response.data.choices[0]); + return response.data.choices[0].text; + } catch (err) { + console.log(err); + return 'Error connecting with API.'; + } +}; + +const gptImageCall = async (prompt: string) => { + try { + const configuration = new Configuration({ + apiKey: process.env.OPENAI_KEY, + }); + const openai = new OpenAIApi(configuration); + const response = await openai.createImage({ + prompt: prompt, + n: 1, + size: '1024x1024', + }); + return response.data.data[0].url; + } catch (err) { + console.error(err); + return; + } +}; + +// const gptEditCall = async (selectedText: string, fullText: string) => { +// try { +// const configuration = new Configuration({ +// apiKey: process.env.OPENAI_KEY, +// }); +// const openai = new OpenAIApi(configuration); +// const response = await openai.createCompletion({ +// model: 'text-davinci-003', +// max_tokens: 256, +// temperature: 0.1, +// prompt: `Replace the phrase ${selectedText} inside of ${fullText}.`, +// }); +// return response.data.choices[0].text.trim(); +// } catch (err) { +// console.log(err); +// return 'Error connecting with API.'; +// } +// }; + +export { gptAPICall, gptImageCall, GPTCallType }; diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 09b45a481..2187f8231 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -68,14 +68,16 @@ class EmptyBox { return ''; } } -export abstract class FInfo { +export class FInfo { description: string = ''; - fieldType?: string; + readOnly: boolean = false; + fieldType?: string = ''; values?: Field[]; // format?: string; // format to display values (e.g, decimal places, $, etc) // parse?: ScriptField; // parse a value from a string - constructor(d: string) { + constructor(d: string, readOnly?: boolean) { this.description = d; + this.readOnly = readOnly ?? false; } } class BoolInfo extends FInfo { @@ -85,16 +87,16 @@ class BoolInfo extends FInfo { class NumInfo extends FInfo { fieldType? = 'number'; values?: number[] = []; - constructor(d: string, values?: number[]) { - super(d); + constructor(d: string, readOnly?: boolean, values?: number[]) { + super(d, readOnly); this.values = values; } } class StrInfo extends FInfo { fieldType? = 'string'; values?: string[] = []; - constructor(d: string, values?: string[]) { - super(d); + constructor(d: string, readOnly?: boolean, values?: string[]) { + super(d, readOnly); this.values = values; } } @@ -102,21 +104,28 @@ class DocInfo extends FInfo { fieldType? = 'Doc'; values?: Doc[] = []; constructor(d: string, values?: Doc[]) { - super(d); + super(d, true); this.values = values; } } class DimInfo extends FInfo { fieldType? = 'DimUnit'; values? = [DimUnit.Pixel, DimUnit.Ratio]; + readOnly = true; } class PEInfo extends FInfo { fieldType? = 'pointerEvents'; values? = ['all', 'none']; + readOnly = true; } class DAInfo extends FInfo { fieldType? = 'dropActionType'; values? = ['alias', 'copy', 'move', 'same', 'proto', 'none']; + readOnly = true; +} +class DateInfo extends FInfo { + fieldType? = 'date'; + values?: DateField[] = []; } type BOOLt = BoolInfo | boolean; type NUMt = NumInfo | number; @@ -125,15 +134,17 @@ type DOCt = DocInfo | Doc; type DIMt = DimInfo | typeof DimUnit.Pixel | typeof DimUnit.Ratio; type PEVt = PEInfo | 'none' | 'all'; type DROPt = DAInfo | dropActionType; +type DATEt = DateInfo | number; export class DocumentOptions { x?: NUMt = new NumInfo('x coordinate of document in a freeform view'); y?: NUMt = new NumInfo('y coordinage of document in a freeform view'); - z?: NUMt = new NumInfo('whether document is in overlay (1) or not (0)', [1, 0]); - system?: BOOLt = new BoolInfo('is this a system created/owned doc'); - type?: STRt = new StrInfo('type of document', Array.from(Object.keys(DocumentType))); + z?: NUMt = new NumInfo('whether document is in overlay (1) or not (0)', false, [1, 0]); + system?: BOOLt = new BoolInfo('is this a system created/owned doc', true); + type?: STRt = new StrInfo('type of document', true, Array.from(Object.keys(DocumentType))); title?: string; + creationDate?: DATEt = new DateInfo('date the document was created', true); _dropAction?: DROPt = new DAInfo("what should happen to this document when it's dropped somewhere else"); - allowOverlayDrop?: BOOLt = new BoolInfo('can documents be dropped onto this document without using dragging title bar or holding down embed key (ctrl)?'); + allowOverlayDrop?: BOOLt = new BoolInfo('can documents be dropped onto this document without using dragging title bar or holding down embed key (ctrl)?', true); childDropAction?: DROPt = new DAInfo("what should happen to the source document when it's dropped onto a child of a collection "); targetDropAction?: DROPt = new DAInfo('what should happen to the source document when ??? '); userColor?: STRt = new StrInfo('color associated with a Dash user (seen in header fields of shared documents)'); @@ -147,6 +158,8 @@ export class DocumentOptions { _panY?: NUMt = new NumInfo('vertical pan location of a freeform view'); _width?: NUMt = new NumInfo('displayed width of a document'); _height?: NUMt = new NumInfo('displayed height of document'); + 'data-nativeWidth'?: NUMt = new NumInfo('native width of data field contents (e.g., the pixel width of an image)'); + 'data-nativeHeight'?: NUMt = new NumInfo('native height of data field contents (e.g., the pixel height of an image)'); _nativeWidth?: NUMt = new NumInfo('native width of document contents (e.g., the pixel width of an image)'); _nativeHeight?: NUMt = new NumInfo('native height of document contents (e.g., the pixel height of an image)'); _nativeDimModifiable?: BOOLt = new BoolInfo('native dimensions can be modified using document decoration reizers'); @@ -197,11 +210,11 @@ export class DocumentOptions { _timecodeToShow?: number; // the time that a document should be displayed (e.g., when an annotation shows up as a video plays) _timecodeToHide?: number; // the time that a document should be hidden _timelineLabel?: boolean; // whether the document exists on a timeline - '_carousel-caption-xMargin'?: NUMt = new NumInfo('x margin of caption inside of a carouself collection'); - '_carousel-caption-yMargin'?: NUMt = new NumInfo('y margin of caption inside of a carouself collection'); - 'icon-nativeWidth'?: NUMt = new NumInfo('native width of icon view'); - 'icon-nativeHeight'?: NUMt = new NumInfo('native height of icon view'); - 'dragFactory-count'?: NUMt = new NumInfo('number of items created from a drag button (used for setting title with incrementing index)'); + '_carousel-caption-xMargin'?: NUMt = new NumInfo('x margin of caption inside of a carouself collection', true); + '_carousel-caption-yMargin'?: NUMt = new NumInfo('y margin of caption inside of a carouself collection', true); + 'icon-nativeWidth'?: NUMt = new NumInfo('native width of icon view', true); + 'icon-nativeHeight'?: NUMt = new NumInfo('native height of icon view', true); + 'dragFactory-count'?: NUMt = new NumInfo('number of items created from a drag button (used for setting title with incrementing index)', true); openFactoryLocation?: string; // an OpenWhere value to place the factory created document openFactoryAsDelegate?: boolean; // lat?: number; @@ -1064,7 +1077,7 @@ export namespace Docs { } export function PileDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _overflow: 'visible', _forceActive: true, _noAutoscroll: true, ...options, _viewType: CollectionViewType.Pile }, id); + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _overflow: 'visible', enableDragWhenActive: true, _forceActive: true, _noAutoscroll: true, ...options, _viewType: CollectionViewType.Pile }, id); } export function LinearDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) { @@ -1194,11 +1207,10 @@ export namespace DocUtils { * @param docs * @param docFilters * @param docRangeFilters - * @param viewSpecScript + * @param parentCollection * Given a list of docs and docFilters, @returns the list of Docs that match those filters */ - export function FilterDocs(docs: Doc[], docFilters: string[], docRangeFilters: string[], viewSpecScript?: ScriptField, parentCollection?: Doc) { - const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs; + export function FilterDocs(childDocs: Doc[], docFilters: string[], docRangeFilters: string[], parentCollection?: Doc) { if (!docFilters?.length && !docRangeFilters?.length) { return childDocs.filter(d => !d.cookies); // remove documents that need a cookie if there are no filters to provide one } diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index abf7313a4..d43419933 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -49,6 +49,7 @@ interface Button { ignoreClick?: boolean; buttonText?: string; backgroundColor?: string; + waitForDoubleClickToClick?: boolean; // fields that do not correspond to DocumentOption fields scripts?: { script?: string; onClick?: string; onDoubleClick?: string } @@ -613,35 +614,36 @@ export class CurrentUserUtils { static freeTools(): Button[] { return [ - { title: "Bottom", icon: "arrows-down-to-line",toolTip: "Make doc topmost", btnType: ButtonType.ClickButton, expertMode: false, funcs: {}, scripts: { onClick: 'sendToBack()'}}, // Only when floating document is selected in freeform - { title: "Top", icon: "arrows-up-to-line", toolTip: "Make doc bottommost", btnType: ButtonType.ClickButton, expertMode: false, funcs: {}, scripts: { onClick: 'bringToFront()'}}, // Only when floating document is selected in freeform + { title: "Bottom", icon: "arrows-down-to-line",toolTip: "Make doc topmost", btnType: ButtonType.ClickButton, expertMode: false, funcs: {}, scripts: { onClick: 'sendToBack()'}}, // Only when floating document is selected in freeform + { title: "Top", icon: "arrows-up-to-line", toolTip: "Make doc bottommost", btnType: ButtonType.ClickButton, expertMode: false, funcs: {}, scripts: { onClick: 'bringToFront()'}}, // Only when floating document is selected in freeform + { title: "Z order", icon: "z", toolTip: "Bring Forward on Drag (double click to set for all)",waitForDoubleClickToClick:true, btnType: ButtonType.ToggleButton, expertMode: false, funcs: {}, scripts: { onClick: 'toggleRaiseOnDrag(false, _readOnly_)', onDoubleClick:`{ return toggleRaiseOnDrag(true, _readOnly_)`}}, // Only when floating document is selected in freeform ] } static viewTools(): Button[] { return [ - { title: "Grid", icon: "border-all", toolTip: "Show Grid", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"grid", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform - { title: "Snap\xA0Lines",icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"snap lines", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform - { title: "View\xA0All", icon: "object-group",toolTip: "Fit all Docs to View",btnType: ButtonType.ToggleButton, expertMode: false, toolType:"viewAll", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform - { title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"clusters", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform - { title: "Arrange", icon: "window", toolTip: "Toggle Auto Arrange", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"arrange", funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform - { title: "Reset", icon: "check", toolTip: "Reset View", btnType: ButtonType.ClickButton, expertMode: false, backgroundColor:"transparent", scripts: { onClick: 'resetView()'}}, // Only when floating document is selected in freeform + { title: "Snap\xA0Lines", icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"snap lines", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform + { title: "Grid", icon: "border-all", toolTip: "Show Grid", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"grid", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform + { title: "View\xA0All", icon: "object-group", toolTip: "Fit all Docs to View",btnType: ButtonType.ToggleButton, expertMode: false, toolType:"viewAll", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform + { title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"clusters", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform + { title: "Arrange",icon: "arrow-down-short-wide",toolTip: "Toggle Auto Arrange",btnType: ButtonType.ToggleButton, expertMode: false, toolType:"arrange", funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform + { title: "Reset", icon: "check", toolTip: "Reset View", btnType: ButtonType.ClickButton, expertMode: false, backgroundColor:"transparent", scripts: { onClick: 'resetView()'}}, // Only when floating document is selected in freeform ] } static textTools():Button[] { return [ { title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, toolType:"font", ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'}, btnList: new List<string>(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) }, - { title: "Size", toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton, toolType:"fontSize", ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions }, - { title: "Color", toolTip: "Font color", btnType: ButtonType.ColorButton, icon: "font", toolType:"fontColor",ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'}}, + { title: "Size", toolTip: "Font size (%size)", btnType: ButtonType.NumberButton, width: 75, toolType:"fontSize", ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions }, + { title: "Color", toolTip: "Font color (%color)", btnType: ButtonType.ColorButton, icon: "font", toolType:"fontColor",ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'}}, { title: "Highlight",toolTip:"Font highlight", btnType: ButtonType.ColorButton, icon: "highlighter", toolType:"highlight",ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'},funcs: {hidden: "IsNoviceMode()"} }, { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", toolType:"bold", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", toolType:"italics", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, { title: "Under", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", toolType:"underline", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, { title: "Bullets", toolTip: "Bullet List", btnType: ButtonType.ToggleButton, icon: "list", toolType:"bullet", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, { title: "#", toolTip: "Number List", btnType: ButtonType.ToggleButton, icon: "list-ol", toolType:"decimal", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, - { title: "Left", toolTip: "Left align", btnType: ButtonType.ToggleButton, icon: "align-left", toolType:"left", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}' }}, - { title: "Center", toolTip: "Center align", btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, - { title: "Right", toolTip: "Right align", btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, + { title: "Left", toolTip: "Left align (Cmd-[)", btnType: ButtonType.ToggleButton, icon: "align-left", toolType:"left", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}' }}, + { title: "Center", toolTip: "Center align (Cmd-\\)",btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, + { title: "Right", toolTip: "Right align (Cmd-])", btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, { title: "Dictate", toolTip: "Dictate", btnType: ButtonType.ToggleButton, icon: "microphone", toolType:"dictation", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'}}, { title: "NoLink", toolTip: "Auto Link", btnType: ButtonType.ToggleButton, icon: "link", toolType:"noAutoLink", expertMode:true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'}, funcs: {hidden: 'IsNoviceMode()'}}, // { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", scripts: {onClick:: 'toggleStrikethrough()'}}, @@ -659,9 +661,8 @@ export class CurrentUserUtils { { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square", toolType:GestureUtils.Gestures.Rectangle, scripts: {onClick:`{ return setActiveTool(self.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(self.toolType, true, _readOnly_);}`} }, { title: "Line", toolTip: "Line (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "minus", toolType:GestureUtils.Gestures.Line, scripts: {onClick:`{ return setActiveTool(self.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(self.toolType, true, _readOnly_);}`} }, { title: "Mask", toolTip: "Mask", btnType: ButtonType.ToggleButton, icon: "user-circle",toolType: "inkMask", scripts: {onClick:'{ return setInkProperty(self.toolType, value, _readOnly_);}'} }, - { title: "Fill", toolTip: "Fill color", btnType: ButtonType.ColorButton, icon: "fill-drip", toolType: "fillColor", ignoreClick: true, scripts: {script: '{ return setInkProperty(self.toolType, value, _readOnly_);}'} }, { title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberButton, toolType: "strokeWidth", ignoreClick: true, scripts: {script: '{ return setInkProperty(self.toolType, value, _readOnly_);}'}, numBtnType: NumButtonType.Slider, numBtnMin: 1}, - { title: "Color", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", toolType: "strokeColor", ignoreClick: true, scripts: {script: '{ return setInkProperty(self.toolType, value, _readOnly_);}'} }, + { title: "Ink", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", toolType: "strokeColor", ignoreClick: true, scripts: {script: '{ return setInkProperty(self.toolType, value, _readOnly_);}'} }, ]; } @@ -689,7 +690,6 @@ export class CurrentUserUtils { { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, width: 20, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}}, // Only when a document is selected { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'}}, { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'}, scripts: { onClick: 'toggleOverlay(_readOnly_)'}}, // Only when floating document is selected in freeform - { title: "Z order", icon: "z", toolTip: "Bring Forward on Drag",btnType: ButtonType.ToggleButton, expertMode: false, toolType:CollectionViewType.Freeform, funcs: {}, scripts: { onClick: 'toggleRaiseOnDrag(_readOnly_)'}}, // Only when floating document is selected in freeform { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 20, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}}, { title: "Num", icon:"",toolTip: "Frame Number (click to toggle edit mode)",btnType: ButtonType.TextButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}}, { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 20, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}}, diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index e01457b4f..3a192f712 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -8,8 +8,7 @@ import { CollectionViewType } from '../documents/DocumentTypes'; import { CollectionDockingView } from '../views/collections/CollectionDockingView'; import { TabDocView } from '../views/collections/TabDocView'; import { LightboxView } from '../views/LightboxView'; -import { MainView } from '../views/MainView'; -import { DocFocusOptions, DocumentView, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView'; +import { DocFocusOptions, DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView'; import { FormattedTextBox } from '../views/nodes/formattedText/FormattedTextBox'; import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox'; import { PresBox } from '../views/nodes/trails'; @@ -181,7 +180,7 @@ export class DocumentManager { static GetContextPath(doc: Opt<Doc>, includeExistingViews?: boolean) { if (!doc) return []; - const srcContext = Cast(doc.context, Doc, null) ?? Cast(doc.annotationOn, Doc, null); + const srcContext = DocCast(doc.annotationOn, DocCast(doc.context)); var containerDocContext = srcContext ? [srcContext, doc] : [doc]; while ( containerDocContext.length && @@ -227,7 +226,7 @@ export class DocumentManager { let rootContextView = docViewPath.shift(); await (rootContextView && this.focusViewsInPath(rootContextView, options, async () => ({ childDocView: docViewPath.shift(), viewSpec: undefined }))); if (options.toggleTarget && (!options.didMove || targetDocView.rootDoc.hidden)) targetDocView.rootDoc.hidden = !targetDocView.rootDoc.hidden; - else if (options.openLocation?.startsWith(OpenWhere.toggle) && !options.didMove && rootContextView) MainView.addDocTabFunc(rootContextView.rootDoc, options.openLocation); + else if (options.openLocation?.startsWith(OpenWhere.toggle) && !options.didMove && rootContextView) DocumentViewInternal.addDocTabFunc(rootContextView.rootDoc, options.openLocation); }; // shows a document by first: @@ -247,9 +246,17 @@ export class DocumentManager { const viewIndex = docContextPath.findIndex(doc => this.getDocumentView(doc)); if (viewIndex !== -1) return res(this.getDocumentView(docContextPath[viewIndex])!); options.didMove = true; - docContextPath.some(doc => TabDocView.Activate(doc)) || MainView.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight); + docContextPath.some(doc => TabDocView.Activate(doc)) || DocumentViewInternal.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight); this.AddViewRenderedCb(docContextPath[0], dv => res(dv)); }); + if (options.openLocation === OpenWhere.lightbox) { + // even if we found the document view, if the target is a lightbox, we try to open it in the lightbox to preserve lightbox semantics (eg, there's only one active doc in the lightbox) + const target = DocCast(targetDoc.annotationOn, targetDoc); + const contextView = this.getDocumentView(DocCast(target.context)); + if (contextView?.docView?._componentView?.addDocTab?.(target, OpenWhere.lightbox)) { + await new Promise<void>(waitres => setTimeout(() => waitres())); + } + } docContextPath.shift(); const childViewIterator = async (docView: DocumentView) => { const innerDoc = docContextPath.shift(); diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 7e6de5e67..b6de5604d 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -11,6 +11,7 @@ import * as globalCssVariables from '../views/global/globalCssVariables.scss'; import { Colors } from '../views/global/globalEnums'; import { DocumentView } from '../views/nodes/DocumentView'; import { ScriptingGlobals } from './ScriptingGlobals'; +import { SelectionManager } from './SelectionManager'; import { SnappingManager } from './SnappingManager'; import { UndoManager } from './UndoManager'; @@ -355,7 +356,7 @@ export namespace DragManager { top: Number.MAX_SAFE_INTEGER, bottom: Number.MIN_SAFE_INTEGER, }; - let rot = 0; + let rot: number[] = []; const docsToDrag = dragData instanceof DocumentDragData ? dragData.draggedDocuments : dragData instanceof AnchorAnnoDragData ? [dragData.dragDocument] : []; const dragElements = eles.map(ele => { // bcz: very hacky -- if dragged element is a freeForm view with a rotation, then extract the rotation in order to apply it to the dragged element @@ -363,9 +364,10 @@ export namespace DragManager { // if the parent isn't a freeform view, then the element's width and height are presumed to match the acutal doc's dimensions (eg, dragging from import sidebar menu) if (ele?.parentElement?.parentElement?.parentElement?.className === 'collectionFreeFormDocumentView-container') { ele = ele.parentElement.parentElement.parentElement; - rot = Number(ele.style.transform.replace(/.*rotate\(([-0-9.e]*)deg\).*/, '$1') || 0); + rot.push(Number(ele.style.transform.replace(/.*rotate\(([-0-9.e]*)deg\).*/, '$1') || 0)); } else { useDim = true; + rot.push(0); } if (!ele.parentNode) dragDiv.appendChild(ele); const dragElement = ele.parentNode === dragDiv ? ele : (ele.cloneNode(true) as HTMLElement); @@ -388,7 +390,7 @@ export namespace DragManager { const rect = ele.getBoundingClientRect(); const w = ele.offsetWidth || rect.width; const h = ele.offsetHeight || rect.height; - const rotR = -((rot < 0 ? rot + 360 : rot) / 180) * Math.PI; + const rotR = -((rot.lastElement() < 0 ? rot.lastElement() + 360 : rot.lastElement()) / 180) * Math.PI; const tl = [0, 0]; const tr = [Math.cos(rotR) * w, Math.sin(-rotR) * w]; const bl = [Math.sin(rotR) * h, Math.cos(-rotR) * h]; @@ -422,7 +424,7 @@ export namespace DragManager { transformOrigin: '0 0', width, height, - transform: `translate(${xs[0]}px, ${ys[0]}px) rotate(${rot}deg) scale(${scaling})`, + transform: `translate(${xs[0]}px, ${ys[0]}px) rotate(${rot.lastElement()}deg) scale(${scaling})`, }); dragLabel.style.transform = `translate(${xs[0]}px, ${ys[0] - 20}px)`; @@ -563,7 +565,7 @@ export namespace DragManager { const moveVec = { x: x - lastPt.x, y: y - lastPt.y }; lastPt = { x, y }; - dragElements.map((dragElement, i) => (dragElement.style.transform = `translate(${(xs[i] += moveVec.x)}px, ${(ys[i] += moveVec.y)}px) rotate(${rot}deg) scale(${scalings[i]})`)); + dragElements.map((dragElement, i) => (dragElement.style.transform = `translate(${(xs[i] += moveVec.x)}px, ${(ys[i] += moveVec.y)}px) rotate(${rot[i]}deg) scale(${scalings[i]})`)); dragLabel.style.transform = `translate(${xs[0]}px, ${ys[0] - 20}px)`; }; const upHandler = (e: PointerEvent) => { @@ -596,7 +598,18 @@ export namespace DragManager { } } -ScriptingGlobals.add(function toggleRaiseOnDrag(readOnly?: boolean) { - if (readOnly) return DragManager.GetRaiseWhenDragged() ? Colors.MEDIUM_BLUE : 'transparent'; - DragManager.SetRaiseWhenDragged(!DragManager.GetRaiseWhenDragged()); +ScriptingGlobals.add(function toggleRaiseOnDrag(forAllDocs: boolean, readOnly?: boolean) { + if (readOnly) { + if (SelectionManager.Views().length) + return SelectionManager.Views().some(dv => dv.rootDoc.raiseWhenDragged) + ? Colors.MEDIUM_BLUE + : SelectionManager.Views().some(dv => dv.rootDoc.raiseWhenDragged === false) + ? 'transparent' + : DragManager.GetRaiseWhenDragged() + ? Colors.MEDIUM_BLUE_ALT + : Colors.PINK; + return DragManager.GetRaiseWhenDragged() ? Colors.PINK : 'transparent'; + } + if (!forAllDocs) SelectionManager.Views().map(dv => (dv.rootDoc.raiseWhenDragged ? (dv.rootDoc.raiseWhenDragged = undefined) : dv.rootDoc.raiseWhenDragged === false ? (dv.rootDoc.raiseWhenDragged = true) : (dv.rootDoc.raiseWhenDragged = false))); + else DragManager.SetRaiseWhenDragged(!DragManager.GetRaiseWhenDragged()); }); diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx new file mode 100644 index 000000000..69f62fc3f --- /dev/null +++ b/src/client/util/RTFMarkup.tsx @@ -0,0 +1,137 @@ +import { action, computed, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { MainViewModal } from '../views/MainViewModal'; + +@observer +export class RTFMarkup extends React.Component<{}> { + static Instance: RTFMarkup; + @observable private isOpen = false; // whether the SharingManager modal is open or not + + // private get linkVisible() { + // return this.targetDoc ? this.targetDoc["acl-" + PublicKey] !== SharingPermissions.None : false; + // } + + @action + public open = () => (this.isOpen = true); + + @action + public close = () => (this.isOpen = false); + + constructor(props: {}) { + super(props); + RTFMarkup.Instance = this; + } + + @observable _stats: { [key: string]: any } | undefined; + + /** + * @returns the main interface of the SharingManager. + */ + @computed get cheatSheet() { + return ( + <div style={{ textAlign: 'initial', height: '100%' }}> + <p> + <b style={{ fontSize: 'larger' }}>{`wiki:phrase`}</b> + {` display wikipedia page for entered text (terminate with carriage return)`} + </p> + <p> + <b style={{ fontSize: 'larger' }}>{`#tag `}</b> + {` add hashtag metadata to document. e.g, #idea`} + </p> + <p> + <b style={{ fontSize: 'larger' }}>{`#, ## ... ###### `}</b> + {` set heading style based on number of '#'s between 1 and 6`} + </p> + <p> + <b style={{ fontSize: 'larger' }}>{`#tag `}</b> + {` add hashtag metadata to document. e.g, #idea`} + </p> + <p> + <b style={{ fontSize: 'larger' }}>{`>> `}</b> + {` add a sidebar text document inline`} + </p> + <p> + <b style={{ fontSize: 'larger' }}>{`\`\` `}</b> + {` create a code snippet block`} + </p> + <p> + <b style={{ fontSize: 'larger' }}>{`cmd-f `}</b> + {` collapse to an inline footnote)`} + </p> + <p> + <b style={{ fontSize: 'larger' }}>{`cmd-e `}</b> + {` collapse to elided text`} + </p> + <p> + <b style={{ fontSize: 'larger' }}>{`cmd-[ `}</b> + {` left justify text`} + </p> + <p> + <b style={{ fontSize: 'larger' }}>{`cmd-\\ `}</b> + {` center text`} + </p> + <p> + <b style={{ fontSize: 'larger' }}>{`cmd-] `}</b> + {` right justify text`} + </p> + <p> + <b style={{ fontSize: 'larger' }}>{`%% `}</b> + {` restore default styling`} + </p> + <p> + <b style={{ fontSize: 'larger' }}>{`%color `}</b> + {` changes text color styling. e.g., %green.`} + </p> + <p> + <b style={{ fontSize: 'larger' }}>{`%num `}</b> + {` set font size. e.g., %10 for 10pt font`} + </p> + <p> + <b style={{ fontSize: 'larger' }}>{`%eq `}</b> + {` creates an equation block for typeset math`} + </p> + <p> + <b style={{ fontSize: 'larger' }}>{`%alt `}</b> + {` switch between primary and alternate text (see bottom right Button for hover options).`} + </p> + <p> + <b style={{ fontSize: 'larger' }}>{`%> `}</b> + {` create a bockquote section. Terminate with 2 carriage returns`} + </p> + <p> + <b style={{ fontSize: 'larger' }}>{`%q `}</b> + {` start a quoted block of text that’s indented on the left and right. Terminate with %q`} + </p> + <p> + <b style={{ fontSize: 'larger' }}>{`%d `}</b> + {` start a block text where the first line is indented`} + </p> + <p> + <b style={{ fontSize: 'larger' }}>{`%h `}</b> + {` start a block of text that begins with a hanging indent`} + </p> + <p> + <b style={{ fontSize: 'larger' }}>{`[:doctitle]] `}</b> + {` hyperlink to document specified by it’s title`} + </p> + <p> + <b style={{ fontSize: 'larger' }}>{`[[fieldname]] `}</b> + {` display value of fieldname`} + </p> + <p> + <b style={{ fontSize: 'larger' }}>{`[[fieldname=value]] `}</b> + {` assign value to fieldname of document and display it`} + </p> + <p> + <b style={{ fontSize: 'larger' }}>{`[[fieldname:doctitle]] `}</b> + {` show value of fieldname from doc specified by it’s title`} + </p> + </div> + ); + } + + render() { + return <MainViewModal contents={this.cheatSheet} isDisplayed={this.isOpen} interactive={true} closeOnExternalClick={this.close} />; + } +} diff --git a/src/client/util/ReplayMovements.ts b/src/client/util/ReplayMovements.ts index 40261985a..22cca4a2e 100644 --- a/src/client/util/ReplayMovements.ts +++ b/src/client/util/ReplayMovements.ts @@ -1,13 +1,11 @@ +import { IReactionDisposer, observable, reaction } from 'mobx'; +import { Doc, IdToDoc } from '../../fields/Doc'; +import { CollectionDockingView } from '../views/collections/CollectionDockingView'; import { CollectionFreeFormView } from '../views/collections/collectionFreeForm'; -import { IReactionDisposer, observable, observe, reaction } from 'mobx'; -import { Doc } from '../../fields/Doc'; +import { OpenWhereMod } from '../views/nodes/DocumentView'; import { VideoBox } from '../views/nodes/VideoBox'; import { DocumentManager } from './DocumentManager'; -import { CollectionDockingView } from '../views/collections/CollectionDockingView'; -import { DocServer } from '../DocServer'; import { Movement, Presentation } from './TrackMovements'; -import { OpenWhereMod } from '../views/nodes/DocumentView'; -import { returnTransparent } from '../../Utils'; export class ReplayMovements { private timers: NodeJS.Timeout[] | null; @@ -61,7 +59,7 @@ export class ReplayMovements { return; } - const docIdtoDoc = this.loadPresentation(presentation); + this.loadPresentation(presentation); this.videoBoxDisposeFunc = reaction( () => ({ playing: videoBox._playing, timeViewed: videoBox.player?.currentTime || 0 }), @@ -94,13 +92,14 @@ export class ReplayMovements { throw '[recordingApi.ts] followMovements() failed: no presentation data'; } - // generate a set of all unique docIds - const docs = new Set<Doc>(); - for (const { doc } of movements) { - if (!docs.has(doc)) docs.add(doc); - } - - return docs; + movements.forEach((movement, i) => { + if (typeof movement.doc === 'string') { + movements[i].doc = IdToDoc(movement.doc); + if (!movements[i].doc) { + console.log('ERROR: tracked doc not found'); + } + } + }); }; // returns undefined if the docView isn't open on the screen diff --git a/src/client/util/ServerStats.tsx b/src/client/util/ServerStats.tsx new file mode 100644 index 000000000..f84ad8598 --- /dev/null +++ b/src/client/util/ServerStats.tsx @@ -0,0 +1,54 @@ +import { action, computed, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { MainViewModal } from '../views/MainViewModal'; +import './SharingManager.scss'; + +@observer +export class ServerStats extends React.Component<{}> { + public static Instance: ServerStats; + @observable private isOpen = false; // whether the SharingManager modal is open or not + + // private get linkVisible() { + // return this.targetDoc ? this.targetDoc["acl-" + PublicKey] !== SharingPermissions.None : false; + // } + + @action + public open = async () => { + /** + * Populates the list of users. + */ + fetch('/stats').then((res: Response) => res.text().then(action(stats => (this._stats = JSON.parse(stats))))); + + this.isOpen = true; + }; + + public close = action(() => { + this.isOpen = false; + }); + + constructor(props: {}) { + super(props); + ServerStats.Instance = this; + } + + @observable _stats: { [key: string]: any } | undefined; + + /** + * @returns the main interface of the SharingManager. + */ + @computed get sharingInterface() { + return ( + <div> + <span>Active users:{this._stats?.socketMap.length}</span> + {this._stats?.socketMap.map((user: any) => ( + <p>{user.username}</p> + ))} + </div> + ); + } + + render() { + return <MainViewModal contents={this.sharingInterface} isDisplayed={this.isOpen} interactive={true} closeOnExternalClick={this.close} />; + } +} diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index a73eda04c..4937866f8 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -5,14 +5,13 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import Select from 'react-select'; import * as RequestPromise from 'request-promise'; -import { AclAdmin, AclPrivate, AclSym, AclUnset, DataSym, Doc, DocListCast, DocListCastAsync, HierarchyMapping, Opt } from '../../fields/Doc'; +import { AclAdmin, AclPrivate, AclSym, AclUnset, DataSym, Doc, DocListCast, DocListCastAsync, HierarchyMapping } from '../../fields/Doc'; import { Id } from '../../fields/FieldSymbols'; import { List } from '../../fields/List'; -import { Cast, NumCast, PromiseValue, StrCast } from '../../fields/Types'; +import { 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'; @@ -21,7 +20,6 @@ import { SearchBox } from '../views/search/SearchBox'; import { DocumentManager } from './DocumentManager'; import { GroupManager, UserOptions } from './GroupManager'; import { GroupMemberView } from './GroupMemberView'; -import { LinkManager } from './LinkManager'; import { SelectionManager } from './SelectionManager'; import './SharingManager.scss'; @@ -581,9 +579,8 @@ export class SharingManager extends React.Component<{}> { </div> ); }); - return ( - <div className={'sharing-interface'}> + <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'}> diff --git a/src/client/util/TrackMovements.ts b/src/client/util/TrackMovements.ts index 2f16307b3..cb8225643 100644 --- a/src/client/util/TrackMovements.ts +++ b/src/client/util/TrackMovements.ts @@ -234,7 +234,7 @@ export class TrackMovements { const movement: Movement = { time, panX, panY, scale, doc }; // add that movement to the current presentation data's movement array - this.currentPresentation.movements && this.currentPresentation.movements.push(movement); + this.currentPresentation.movements?.push(movement); }; // method that concatenates an array of presentatations into one diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index a59189fd2..eba55e30c 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -141,29 +141,25 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>() const effectiveAcl = GetEffectiveAcl(this.dataDoc); const indocs = doc instanceof Doc ? [doc] : doc; const docs = indocs.filter(doc => [AclEdit, AclAdmin].includes(effectiveAcl) || GetEffectiveAcl(doc) === AclAdmin); - if (docs.length) { - docs.map(doc => { - Doc.SetInPlace(doc, 'followLinkToggle', undefined, true); - doc.annotationOn === this.props.Document && Doc.SetInPlace(doc, 'annotationOn', undefined, true); + + docs.forEach(doc => doc.annotationOn === this.props.Document && Doc.SetInPlace(doc, 'annotationOn', undefined, true)); + const targetDataDoc = this.dataDoc; + const value = DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]); + const toRemove = value.filter(v => docs.includes(v)); + + if (toRemove.length !== 0) { + const recent = Doc.MyRecentlyClosed; + toRemove.forEach(doc => { + leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey); + Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc); + doc.context = undefined; + if (recent) { + Doc.RemoveDocFromList(recent, 'data', doc); + doc.type !== DocumentType.LOADING && Doc.AddDocToList(recent, 'data', doc, undefined, true, true); + } }); - const targetDataDoc = this.dataDoc; - const value = DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]); - const toRemove = value.filter(v => docs.includes(v)); - - if (toRemove.length !== 0) { - const recent = Doc.MyRecentlyClosed; - toRemove.forEach(doc => { - leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey); - Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc); - doc.context = undefined; - if (recent) { - Doc.RemoveDocFromList(recent, 'data', doc); - doc.type !== DocumentType.LOADING && Doc.AddDocToList(recent, 'data', doc, undefined, true, true); - } - }); - this.isAnyChildContentActive() && this.props.select(false); - return true; - } + this.isAnyChildContentActive() && this.props.select(false); + return true; } return false; @@ -190,46 +186,44 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>() return false; } const targetDataDoc = this.props.Document[DataSym]; - const docList = DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]); - const added = docs.filter(d => !docList.includes(d)); const effectiveAcl = GetEffectiveAcl(targetDataDoc); + if (effectiveAcl === AclPrivate || effectiveAcl === AclReadonly) { + return false; + } + const added = docs; if (added.length) { - if (effectiveAcl === AclPrivate || effectiveAcl === AclReadonly) { - return false; - } else { - if (this.props.Document[AclSym] && Object.keys(this.props.Document[AclSym]).length) { - added.forEach(d => { - for (const [key, value] of Object.entries(this.props.Document[AclSym])) { - if (d.author === denormalizeEmail(key.substring(4)) && !d.aliasOf) distributeAcls(key, SharingPermissions.Admin, d); - } - }); - } + if (this.props.Document[AclSym] && Object.keys(this.props.Document[AclSym]).length) { + added.forEach(d => { + for (const key of Object.keys(this.props.Document[AclSym])) { + if (d.author === denormalizeEmail(key.substring(4)) && !d.aliasOf) distributeAcls(key, SharingPermissions.Admin, d); + } + }); + } - if (effectiveAcl === AclAugment) { - added.map(doc => { - if ([AclAdmin, AclEdit].includes(GetEffectiveAcl(doc)) && Doc.ActiveDashboard) inheritParentAcls(Doc.ActiveDashboard, doc); + if (effectiveAcl === AclAugment) { + added.map(doc => { + if ([AclAdmin, AclEdit].includes(GetEffectiveAcl(doc)) && Doc.ActiveDashboard) inheritParentAcls(Doc.ActiveDashboard, doc); + doc.context = this.props.Document; + if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document; + Doc.AddDocToList(targetDataDoc, annotationKey ?? this.annotationKey, doc); + }); + } else { + added + .filter(doc => [AclAdmin, AclEdit].includes(GetEffectiveAcl(doc))) + .map(doc => { + // only make a pushpin if we have acl's to edit the document + //DocUtils.LeavePushpin(doc); + doc._stayInCollection = undefined; doc.context = this.props.Document; - if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document; - Doc.AddDocToList(targetDataDoc, annotationKey ?? this.annotationKey, doc); + if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.rootDoc; + + Doc.ActiveDashboard && inheritParentAcls(Doc.ActiveDashboard, doc); }); - } else { - added - .filter(doc => [AclAdmin, AclEdit].includes(GetEffectiveAcl(doc))) - .map(doc => { - // only make a pushpin if we have acl's to edit the document - //DocUtils.LeavePushpin(doc); - doc._stayInCollection = undefined; - doc.context = this.props.Document; - if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document; - - Doc.ActiveDashboard && inheritParentAcls(Doc.ActiveDashboard, doc); - }); - const annoDocs = targetDataDoc[annotationKey ?? this.annotationKey] as List<Doc>; - if (annoDocs instanceof List) annoDocs.push(...added); - else targetDataDoc[annotationKey ?? this.annotationKey] = new List<Doc>(added); - targetDataDoc[(annotationKey ?? this.annotationKey) + '-lastModified'] = new DateField(new Date(Date.now())); - } + const annoDocs = targetDataDoc[annotationKey ?? this.annotationKey] as List<Doc>; + if (annoDocs instanceof List) annoDocs.push(...added); + else targetDataDoc[annotationKey ?? this.annotationKey] = new List<Doc>(added); + targetDataDoc[(annotationKey ?? this.annotationKey) + '-lastModified'] = new DateField(new Date(Date.now())); } } return true; diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 8c2ced55a..30e41b06c 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -303,10 +303,6 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV onPointerEnter={action(e => (this.subEndLink = (pinLayout ? 'Layout' : '') + (pinLayout && pinContent ? ' &' : '') + (pinContent ? ' Content' : '')))} onPointerLeave={action(e => (this.subEndLink = ''))} onClick={e => { - const docs = this.props - .views() - .filter(v => v) - .map(dv => dv!.rootDoc); this.view0 && DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.view0.props.Document, true, this.view0, { pinDocLayout: pinLayout, diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 9a4aaf00e..85d36dbf8 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -12,7 +12,7 @@ import { InkField } from '../../fields/InkField'; import { ScriptField } from '../../fields/ScriptField'; import { Cast, NumCast, StrCast } from '../../fields/Types'; import { GetEffectiveAcl } from '../../fields/util'; -import { emptyFunction, numberValue, returnFalse, setupMoveUpEvents, Utils } from '../../Utils'; +import { aggregateBounds, emptyFunction, numberValue, returnFalse, setupMoveUpEvents, Utils } from '../../Utils'; import { Docs } from '../documents/Documents'; import { DocumentType } from '../documents/DocumentTypes'; import { DragManager } from '../util/DragManager'; @@ -34,6 +34,8 @@ import React = require('react'); import { RichTextField } from '../../fields/RichTextField'; import { LinkFollower } from '../util/LinkFollower'; import _ = require('lodash'); +import { DocumentManager } from '../util/DocumentManager'; +import { isUndefined } from 'lodash'; @observer export class DocumentDecorations extends React.Component<{ PanelWidth: number; PanelHeight: number; boundsLeft: number; boundsTop: number }, { value: string }> { @@ -288,12 +290,11 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P openDoc = DocListCast(openDoc.aliases).find(alias => !alias.context) ?? Doc.MakeAlias(openDoc); Doc.deiconifyView(openDoc); } - selectedDocs[0].props.addDocTab(openDoc, OpenWhere.lightbox); - // LightboxView.SetLightboxDoc( - // openDoc, - // undefined, - // selectedDocs.slice(1).map(view => view.props.Document) - // ); + LightboxView.SetLightboxDoc( + openDoc, + undefined, + selectedDocs.slice(1).map(view => view.props.Document) + ); } } SelectionManager.DeselectAll(); @@ -509,73 +510,86 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P dragRight = false, dragBotRight = false, dragTop = false; - let dX = 0, - dY = 0, - dW = 0, - dH = 0; + let dXin = 0, + dYin = 0, + dWin = 0, + dHin = 0; switch (this._resizeHdlId.split(' ')[0]) { case '': break; case 'documentDecorations-topLeftResizer': - dX = -1; - dY = -1; - dW = -move[0]; - dH = -move[1]; + dXin = -1; + dYin = -1; + dWin = -move[0]; + dHin = -move[1]; break; case 'documentDecorations-topRightResizer': - dW = move[0]; - dY = -1; - dH = -move[1]; + dWin = move[0]; + dYin = -1; + dHin = -move[1]; break; case 'documentDecorations-topResizer': - dY = -1; - dH = -move[1]; + dYin = -1; + dHin = -move[1]; dragTop = true; break; case 'documentDecorations-bottomLeftResizer': - dX = -1; - dW = -move[0]; - dH = move[1]; + dXin = -1; + dWin = -move[0]; + dHin = move[1]; break; case 'documentDecorations-bottomRightResizer': - dW = move[0]; - dH = move[1]; + dWin = move[0]; + dHin = move[1]; dragBotRight = true; break; case 'documentDecorations-bottomResizer': - dH = move[1]; + dHin = move[1]; dragBottom = true; break; case 'documentDecorations-leftResizer': - dX = -1; - dW = -move[0]; + dXin = -1; + dWin = -move[0]; break; case 'documentDecorations-rightResizer': - dW = move[0]; + dWin = move[0]; dragRight = true; break; } - SelectionManager.Views().forEach( + const isGroup = first.rootDoc._isGroup ? first.rootDoc : undefined; + const scaleViews = isGroup ? DocListCast(isGroup.data).map(doc => DocumentManager.Instance.getFirstDocumentView(doc)!) : SelectionManager.Views(); + const aggBounds = aggregateBounds(scaleViews.map(view => view.rootDoc) as any, 0, 0); + const refWidth = aggBounds.r - aggBounds.x; + const refHeight = aggBounds.b - aggBounds.y; + const scaleRefPt = first.props + .ScreenToLocalTransform() + .inverse() + .transformPoint( + NumCast(isGroup?._xPadding) + (dXin ? refWidth : 0), // + NumCast(isGroup?._yPadding) + (dYin ? refHeight : 0) + ); + scaleViews.forEach( action((docView: DocumentView) => { if (e.ctrlKey && !Doc.NativeHeight(docView.props.Document)) docView.toggleNativeDimensions(); - if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) { - const doc = Document(docView.rootDoc); - if (doc.nativeHeightUnfrozen && !NumCast(doc.nativeHeight)) { + if (dXin !== 0 || dYin !== 0 || dWin !== 0 || dHin !== 0) { + const doc = docView.rootDoc; + const refCent = docView.props.ScreenToLocalTransform().transformPoint(scaleRefPt[0], scaleRefPt[1]); + + if (doc.nativeHeightUnfrozen && !NumCast(doc.nativeHeight) && doc._nativeWidth !== undefined) { doc._nativeHeight = (NumCast(doc._height) / NumCast(doc._width, 1)) * docView.nativeWidth; } const nwidth = docView.nativeWidth; const nheight = docView.nativeHeight; - let docheight = doc._height || 0; - let docwidth = doc._width || 0; - const width = docwidth; - let height = docheight || (nheight / nwidth) * width; - height = !height || isNaN(height) ? 20 : height; + const docwidth = NumCast(doc._width); + let docheight = (hgt => (!hgt || isNaN(hgt) ? 20 : hgt))(NumCast(doc._height) || (nheight / nwidth) * docwidth); + let dW = docwidth * (dWin / refWidth); + let dH = docheight * (dHin / refHeight); const scale = docView.props.ScreenToLocalTransform().Scale; const modifyNativeDim = (e.ctrlKey || doc.forceReflow) && doc.nativeDimModifiable && ((!dragBottom && !dragTop) || e.ctrlKey || doc.nativeHeightUnfrozen); if (nwidth && nheight) { - if (nwidth / nheight !== width / height && !dragBottom && !dragTop) { - height = (nheight / nwidth) * width; + if (nwidth / nheight !== docwidth / docheight && !dragBottom && !dragTop) { + docheight = (nheight / nwidth) * docwidth; } if (modifyNativeDim && !dragBottom && !dragTop) { // ctrl key enables modification of the nativeWidth or nativeHeight durin the interaction @@ -583,21 +597,25 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P else dW = (dH * nwidth) / nheight; } } - let actualdW = Math.max(width + dW * scale, 20); - let actualdH = Math.max(height + dH * scale, 20); + let actualdW = Math.max(docwidth + dW * scale, 20); + let actualdH = Math.max(docheight + dH * scale, 20); + let dX = !dWin ? 0 : scale * refCent[0] * (1 - (1 + dWin / refWidth)); + let dY = !dHin ? 0 : scale * refCent[1] * (1 - (1 + dHin / refHeight)); const preserveNativeDim = doc._nativeHeightUnfrozen === false && doc._nativeDimModifiable === false; const fixedAspect = nwidth && nheight && (!doc._fitWidth || preserveNativeDim || e.ctrlKey || doc.nativeHeightUnfrozen || doc.nativeDimModifiable); if (fixedAspect) { if ((Math.abs(dW) > Math.abs(dH) && ((!dragBottom && !dragTop) || !modifyNativeDim)) || dragRight) { if (dragRight && modifyNativeDim) { if (Doc.NativeWidth(doc)) { - doc._nativeWidth = (actualdW / (doc._width || 1)) * Doc.NativeWidth(doc); + doc._nativeWidth = (actualdW / (docwidth || 1)) * Doc.NativeWidth(doc); } } else { if (!doc._fitWidth || preserveNativeDim) { actualdH = (nheight / nwidth) * actualdW; doc._height = actualdH; - } else if (!modifyNativeDim || dragBotRight) doc._height = actualdH; + } else if (!modifyNativeDim || dragBotRight) { + doc._height = actualdH; + } } doc._width = actualdW; } else { @@ -605,21 +623,23 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P // frozen web pages, PDFs, and some RTFS have frozen nativewidth/height. But they are marked to allow their nativeHeight // to be explicitly modified with fitWidth and vertical resizing. (ie, with fitWidth they can't grow horizontally to match // a vertical resize so it makes more sense to change their nativeheight even if the ctrl key isn't used) - doc._nativeHeight = (actualdH / (doc._height || 1)) * Doc.NativeHeight(doc); + doc._nativeHeight = (actualdH / (docheight || 1)) * Doc.NativeHeight(doc); doc._autoHeight = false; } else { if (!doc._fitWidth || preserveNativeDim) { actualdW = (nwidth / nheight) * actualdH; doc._width = actualdW; - } else if (!modifyNativeDim || dragBotRight) doc._width = actualdW; + } else if (!modifyNativeDim || dragBotRight) { + doc._width = actualdW; + } } if (!modifyNativeDim) { - actualdH = Math.min((nheight / nwidth) * NumCast(doc._width), actualdH); - doc._height = actualdH; - } else doc._height = actualdH; + actualdH = Math.min((nheight / nwidth) * docwidth, actualdH); + } + doc._height = actualdH; } } else { - const rotCtr = [NumCast(doc._width) / 2, NumCast(doc._height) / 2]; + const rotCtr = [docwidth / 2, docheight / 2]; const tlRotated = Utils.rotPt(-rotCtr[0], -rotCtr[1], (NumCast(doc._rotation) / 180) * Math.PI); const maxHeight = doc.nativeHeightUnfrozen || !nheight ? 0 : Math.max(nheight, NumCast(doc.scrollHeight, NumCast(doc[docView.LayoutFieldKey + '-scrollHeight']))) * docView.NativeDimScaling(); @@ -632,8 +652,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P doc.x = NumCast(doc.x) + tlRotated.x + rotCtr[0] - (tlRotated2.x + rotCtr2[0]); // doc shifts by amount topleft moves because rotation is about center of doc doc.y = NumCast(doc.y) + tlRotated.y + rotCtr[1] - (tlRotated2.y + rotCtr2[1]); } - doc.x = (doc.x || 0) + dX * (actualdW - docwidth); - doc.y = (doc.y || 0) + (dragBottom ? 0 : dY * (actualdH - docheight)); + doc.x = NumCast(doc.x) + dX; + doc.y = NumCast(doc.y) + dY; doc._lastModified = new DateField(); } const val = this._dragHeights.get(docView.layoutDoc); @@ -726,7 +746,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P } // hide the decorations if the parent chooses to hide it or if the document itself hides it const hideDecorations = seldocview.props.hideDecorations || seldocview.rootDoc.hideDecorations; - const hideResizers = hideDecorations || seldocview.props.hideResizeHandles || seldocview.rootDoc.hideResizeHandles || seldocview.rootDoc._isGroup || this._isRounding || this._isRotating; + const hideResizers = hideDecorations || seldocview.props.hideResizeHandles || seldocview.rootDoc.hideResizeHandles || this._isRounding || this._isRotating; const hideTitle = hideDecorations || seldocview.props.hideDecorationTitle || seldocview.rootDoc.hideDecorationTitle || this._isRounding || this._isRotating; const hideDocumentButtonBar = hideDecorations || seldocview.props.hideDocumentButtonBar || seldocview.rootDoc.hideDocumentButtonBar || this._isRounding || this._isRotating; // if multiple documents have been opened at the same time, then don't show open button @@ -781,7 +801,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P const useLock = bounds.r - bounds.x > 135 && seldocview.props.CollectionFreeFormDocumentView; const useRotation = !hideResizers && seldocview.rootDoc.type !== DocumentType.EQUATION && seldocview.props.CollectionFreeFormDocumentView; // when do we want an object to not rotate? - const rotation = NumCast(seldocview.rootDoc._rotation); + const rotation = SelectionManager.Views().length == 1 ? NumCast(seldocview.rootDoc._rotation) : 0; const resizerScheme = colorScheme ? 'documentDecorations-resizer' + colorScheme : ''; @@ -828,7 +848,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P left: bounds.x - this._resizeBorderWidth / 2, top: bounds.y - this._resizeBorderWidth / 2, pointerEvents: DocumentDecorations.Instance.AddToSelection || this.Interacting ? 'none' : 'all', - display: SelectionManager.Views().length <= 1 ? 'none' : undefined, + display: SelectionManager.Views().length <= 1 || hideDecorations ? 'none' : undefined, }} onPointerDown={this.onBackgroundDown} onContextMenu={e => { diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index 164b6c57a..d1311a60a 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -106,6 +106,12 @@ export class EditableView extends React.Component<EditableProps> { case ':': this.props.menuCallback?.(e.currentTarget.getBoundingClientRect().x, e.currentTarget.getBoundingClientRect().y); break; + case 'ArrowUp': + case 'ArrowDown': + case 'ArrowLeft': + case 'ArrowRight': + e.stopPropagation(); + break; case 'Shift': case 'Alt': case 'Meta': diff --git a/src/client/views/FilterPanel.scss b/src/client/views/FilterPanel.scss index 7f907c8d4..c903f29ee 100644 --- a/src/client/views/FilterPanel.scss +++ b/src/client/views/FilterPanel.scss @@ -33,9 +33,10 @@ // } .filterBox-select { - // width: 90%; + display: flex; + width: 100%; margin-top: 5px; - // margin-bottom: 15px; + background: white; } .filterBox-saveBookmark { @@ -150,8 +151,8 @@ .filterBox-treeView { display: flex; flex-direction: column; - width: 200px; - position: absolute; + width: 100%; + position: relative; right: 0; top: 0; z-index: 1; @@ -184,6 +185,7 @@ display: inline-block; width: 100%; margin-bottom: 10px; - //height: calc(100% - 30px); + margin-left: 5px; + overflow: auto; } } diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx index d35494f26..a237249c1 100644 --- a/src/client/views/FilterPanel.tsx +++ b/src/client/views/FilterPanel.tsx @@ -51,10 +51,12 @@ export class FilterPanel extends React.Component<filterProps> { const keys = new Set<string>(noviceFields); this.allDocs.forEach(doc => SearchBox.documentKeys(doc).filter(key => keys.add(key))); - return Array.from(keys.keys()) + const sortedKeys = Array.from(keys.keys()) .filter(key => key[0]) .filter(key => key[0] === '#' || key.indexOf('lastModified') !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith('_')) || noviceFields.includes(key) || !Doc.noviceMode) .sort(); + noviceFields.forEach(key => sortedKeys.splice(sortedKeys.indexOf(key), 1)); + return [...noviceFields, ...sortedKeys]; } /** @@ -129,7 +131,7 @@ export class FilterPanel extends React.Component<filterProps> { maxVal = Math.max(num, maxVal); } }); - if (facetHeader === 'text' || (facetValues.rtFields / allCollectionDocs.length > 0.1 && facetValues.rtFields > 20)) { + if (facetHeader === 'text' || (facetValues.rtFields / allCollectionDocs.length > 0.1 && facetValues.strings.length > 20)) { this._chosenFacets.set(facetHeader, 'text'); } else if (facetHeader !== 'tags' && nonNumbers / facetValues.strings.length < 0.1) { } else { @@ -140,7 +142,7 @@ export class FilterPanel extends React.Component<filterProps> { facetValues = (facetHeader: string) => { const allCollectionDocs = new Set<Doc>(); SearchBox.foreachRecursiveDoc(this.targetDocChildren, (depth: number, doc: Doc) => allCollectionDocs.add(doc)); - const set = new Set<string>(); + const set = new Set<string>([String.fromCharCode(127) + '--undefined--']); if (facetHeader === 'tags') allCollectionDocs.forEach(child => Field.toString(child[facetHeader] as Field) @@ -158,32 +160,29 @@ export class FilterPanel extends React.Component<filterProps> { let nonNumbers = 0; facetValues.map(val => Number.isNaN(Number(val)) && nonNumbers++); - const facetValueDocSet = (nonNumbers / facetValues.length > 0.1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2))).map(facetValue => { - return facetValue; - }); - return facetValueDocSet; + return nonNumbers / facetValues.length > 0.1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2)); }; render() { const options = this._allFacets.filter(facet => this.currentFacets.indexOf(facet) === -1).map(facet => ({ value: facet, label: facet })); return ( - <div className="filterBox-treeView" style={{ position: 'relative', width: '100%' }}> - <div className="filterBox-select-bool"> - <select className="filterBox-selection" onChange={action(e => this.targetDoc && (this.targetDoc._filterBoolean = (e.target as any).value))} defaultValue={StrCast(this.targetDoc?.filterBoolean)}> - {['AND', 'OR'].map(bool => ( - <option value={bool} key={bool}> - {bool} - </option> - ))} - </select> - <div className="filterBox-select-text">filters together</div> - </div> - + <div className="filterBox-treeView"> <div className="filterBox-select"> - <Select placeholder="Add a filter..." options={options} isMulti={false} onChange={val => this.facetClick((val as UserOptions).value)} onKeyDown={e => e.stopPropagation()} value={null} closeMenuOnSelect={true} /> + <div style={{ width: '100%' }}> + <Select placeholder="Add a filter..." options={options} isMulti={false} onChange={val => this.facetClick((val as UserOptions).value)} onKeyDown={e => e.stopPropagation()} value={null} closeMenuOnSelect={true} /> + </div> + <div className="filterBox-select-bool"> + <select className="filterBox-selection" onChange={action(e => this.targetDoc && (this.targetDoc._filterBoolean = (e.target as any).value))} defaultValue={StrCast(this.targetDoc?.filterBoolean)}> + {['AND', 'OR'].map(bool => ( + <option value={bool} key={bool}> + {bool} + </option> + ))} + </select> + </div>{' '} </div> - <div className="filterBox-tree" key="tree" style={{ overflow: 'auto' }}> + <div className="filterBox-tree" key="tree"> {Array.from(this.activeFacets.keys()).map(facetHeader => ( <div> {facetHeader} diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 13faae783..b04769c1e 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -658,9 +658,9 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> { } possibilities.push(...wR?.alternates?.map((a: any) => a.recognizedString)); } - const r = Math.max(this.svgBounds.right, ...this._strokes.map(s => this.getBounds(s).right)); - const l = Math.min(this.svgBounds.left, ...this._strokes.map(s => this.getBounds(s).left)); - const t = Math.min(this.svgBounds.top, ...this._strokes.map(s => this.getBounds(s).top)); + const r = Math.max(this.svgBounds.right, ...this._strokes.map(s => GestureOverlay.getBounds(s).right)); + const l = Math.min(this.svgBounds.left, ...this._strokes.map(s => GestureOverlay.getBounds(s).left)); + const t = Math.min(this.svgBounds.top, ...this._strokes.map(s => GestureOverlay.getBounds(s).top)); // if we receive any word results from cognitive services, display them runInAction(() => { @@ -891,7 +891,7 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> { detail: { points, gesture, - bounds: this.getBounds(points), + bounds: GestureOverlay.getBounds(points), text, }, }) @@ -899,7 +899,7 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> { ); }; - getBounds = (stroke: InkData, pad?: boolean) => { + public static getBounds = (stroke: InkData, pad?: boolean) => { const padding = pad ? [-20000, 20000] : []; const xs = [...padding, ...stroke.map(p => p.X)]; const ys = [...padding, ...stroke.map(p => p.Y)]; @@ -911,7 +911,7 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> { }; @computed get svgBounds() { - return this.getBounds(this._points); + return GestureOverlay.getBounds(this._points); } @computed get elements() { diff --git a/src/client/views/InkStroke.scss b/src/client/views/InkStroke.scss index bed7caf7f..f504890a5 100644 --- a/src/client/views/InkStroke.scss +++ b/src/client/views/InkStroke.scss @@ -39,5 +39,8 @@ &:hover { background: #9f9f9f0a; } + > .formattedTextBox { + position: relative; + } } } diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index 69eec8456..c18a89481 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -63,10 +63,8 @@ export class LightboxView extends React.Component<LightboxViewProps> { Doc.ActiveTool = InkTool.None; MainView.Instance._exploreMode = false; } else { - if (doc) { - const l = DocUtils.MakeLinkToActiveAudio(() => doc).lastElement(); - l && (Cast(l.anchor2, Doc, null).backgroundColor = 'lightgreen'); - } + const l = DocUtils.MakeLinkToActiveAudio(() => doc).lastElement(); + l && (Cast(l.anchor2, Doc, null).backgroundColor = 'lightgreen'); CollectionStackedTimeline.CurrentlyPlaying?.forEach(dv => dv.ComponentView?.Pause?.()); //TabDocView.PinDoc(doc, { hidePresBox: true }); this._history ? this._history.push({ doc, target }) : (this._history = [{ doc, target }]); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index ccc9a7215..5af9a9f9a 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -14,7 +14,7 @@ import { StrCast } from '../../fields/Types'; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents, Utils } from '../../Utils'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { DocServer } from '../DocServer'; -import { Docs, DocUtils } from '../documents/Documents'; +import { Docs } from '../documents/Documents'; import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; import { CaptureManager } from '../util/CaptureManager'; import { DocumentManager } from '../util/DocumentManager'; @@ -22,8 +22,10 @@ import { GroupManager } from '../util/GroupManager'; import { HistoryUtil } from '../util/History'; import { Hypothesis } from '../util/HypothesisUtils'; import { ReportManager } from '../util/ReportManager'; +import { RTFMarkup } from '../util/RTFMarkup'; import { ScriptingGlobals } from '../util/ScriptingGlobals'; import { SelectionManager } from '../util/SelectionManager'; +import { ServerStats } from '../util/ServerStats'; import { ColorScheme, SettingsManager } from '../util/SettingsManager'; import { SharingManager } from '../util/SharingManager'; import { SnappingManager } from '../util/SnappingManager'; @@ -40,7 +42,7 @@ import { DashboardView } from './DashboardView'; import { DictationOverlay } from './DictationOverlay'; import { DocumentDecorations } from './DocumentDecorations'; import { GestureOverlay } from './GestureOverlay'; -import { TOPBAR_HEIGHT, LEFT_MENU_WIDTH } from './global/globalCssVariables.scss'; +import { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } from './global/globalCssVariables.scss'; import { Colors } from './global/globalEnums'; import { KeyManager } from './GlobalKeyHandler'; import { InkTranscription } from './InkTranscription'; @@ -49,7 +51,7 @@ import { LinkMenu } from './linking/LinkMenu'; import './MainView.scss'; import { AudioBox } from './nodes/AudioBox'; import { DocumentLinksButton } from './nodes/DocumentLinksButton'; -import { DocumentView, OpenWhere, OpenWhereMod } from './nodes/DocumentView'; +import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from './nodes/DocumentView'; import { DashFieldViewMenu } from './nodes/formattedText/DashFieldView'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import { RichTextMenu } from './nodes/formattedText/RichTextMenu'; @@ -222,6 +224,7 @@ export class MainView extends React.Component { constructor(props: Readonly<{}>) { super(props); + DocumentViewInternal.addDocTabFunc = MainView.addDocTabFunc_impl; MainView.Instance = this; DashboardView._urlState = HistoryUtil.parseUrl(window.location) || ({} as any); @@ -243,7 +246,9 @@ export class MainView extends React.Component { library.add( ...[ + fa.faExclamationCircle, fa.faEdit, + fa.faArrowDownShortWide, fa.faTrash, fa.faTrashAlt, fa.faShare, @@ -275,6 +280,7 @@ export class MainView extends React.Component { fa.faHandPointRight, fa.faCompass, fa.faSnowflake, + fa.faStar, fa.faMicrophone, fa.faKeyboard, fa.faQuestion, @@ -312,6 +318,8 @@ export class MainView extends React.Component { fa.faArrowUp, fa.faBolt, fa.faBullseye, + fa.faTurnUp, + fa.faTurnDown, fa.faCaretUp, fa.faCat, fa.faCheck, @@ -322,6 +330,7 @@ export class MainView extends React.Component { fa.faClone, fa.faCloudUploadAlt, fa.faCommentAlt, + fa.faCommentDots, fa.faCompressArrowsAlt, fa.faCut, fa.faEllipsisV, @@ -579,7 +588,7 @@ export class MainView extends React.Component { Document={this.headerBarDoc} DataDoc={undefined} addDocument={undefined} - addDocTab={MainView.addDocTabFunc} + addDocTab={DocumentViewInternal.addDocTabFunc} pinToPres={emptyFunction} docViewPath={returnEmptyDoclist} styleProvider={DefaultStyleProvider} @@ -614,7 +623,7 @@ export class MainView extends React.Component { Document={this.mainContainer!} DataDoc={undefined} addDocument={undefined} - addDocTab={MainView.addDocTabFunc} + addDocTab={DocumentViewInternal.addDocTabFunc} pinToPres={emptyFunction} docViewPath={returnEmptyDoclist} styleProvider={this._hideUI ? DefaultStyleProvider : undefined} @@ -683,7 +692,7 @@ export class MainView extends React.Component { sidebarScreenToLocal = () => new Transform(0, -this.topOfSidebarDoc, 1); mainContainerXf = () => this.sidebarScreenToLocal().translate(-this.leftScreenOffsetOfMainDocView, 0); - static addDocTabFunc = (doc: Doc, location: OpenWhere): boolean => { + static addDocTabFunc_impl = (doc: Doc, location: OpenWhere): boolean => { const whereFields = doc._viewType === CollectionViewType.Docking ? [OpenWhere.dashboard] : location.split(':'); const keyValue = whereFields[1]?.includes('KeyValue'); const whereMods: OpenWhereMod = whereFields.length > 1 ? (whereFields[1].replace('KeyValue', '') as OpenWhereMod) : OpenWhereMod.none; @@ -711,7 +720,7 @@ export class MainView extends React.Component { Document={this._sidebarContent.proto || this._sidebarContent} DataDoc={undefined} addDocument={undefined} - addDocTab={MainView.addDocTabFunc} + addDocTab={DocumentViewInternal.addDocTabFunc} pinToPres={emptyFunction} docViewPath={returnEmptyDoclist} styleProvider={this._sidebarContent.proto === Doc.MyDashboards || this._sidebarContent.proto === Doc.MyFilesystem ? DashboardStyleProvider : DefaultStyleProvider} @@ -743,7 +752,7 @@ export class MainView extends React.Component { Document={Doc.MyLeftSidebarMenu} DataDoc={undefined} addDocument={undefined} - addDocTab={MainView.addDocTabFunc} + addDocTab={DocumentViewInternal.addDocTabFunc} pinToPres={emptyFunction} rootSelected={returnTrue} removeDocument={returnFalse} @@ -805,7 +814,7 @@ export class MainView extends React.Component { </div> )} <div className="properties-container" style={{ width: this.propertiesWidth() }}> - {this.propertiesWidth() < 10 ? null : <PropertiesView styleProvider={DefaultStyleProvider} addDocTab={MainView.addDocTabFunc} width={this.propertiesWidth()} height={this.propertiesHeight()} />} + {this.propertiesWidth() < 10 ? null : <PropertiesView styleProvider={DefaultStyleProvider} addDocTab={DocumentViewInternal.addDocTabFunc} width={this.propertiesWidth()} height={this.propertiesHeight()} />} </div> </div> </div> @@ -884,7 +893,7 @@ export class MainView extends React.Component { docViewPath={returnEmptyDoclist} moveDocument={this.moveButtonDoc} addDocument={this.addButtonDoc} - addDocTab={MainView.addDocTabFunc} + addDocTab={DocumentViewInternal.addDocTabFunc} pinToPres={emptyFunction} removeDocument={this.remButtonDoc} ScreenToLocalTransform={this.buttonBarXf} @@ -962,6 +971,8 @@ export class MainView extends React.Component { {this.inkResources} <DictationOverlay /> <SharingManager /> + <ServerStats /> + <RTFMarkup /> <SettingsManager /> <ReportManager /> <CaptureManager /> diff --git a/src/client/views/MainViewModal.tsx b/src/client/views/MainViewModal.tsx index 55dee005d..32997a944 100644 --- a/src/client/views/MainViewModal.tsx +++ b/src/client/views/MainViewModal.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import "./MainViewModal.scss"; +import './MainViewModal.scss'; import { observer } from 'mobx-react'; export interface MainViewOverlayProps { @@ -15,31 +15,38 @@ export interface MainViewOverlayProps { @observer export class MainViewModal extends React.Component<MainViewOverlayProps> { - render() { const p = this.props; const dialogueOpacity = p.dialogueBoxDisplayedOpacity || 1; const overlayOpacity = p.overlayDisplayedOpacity || 0.4; - return !p.isDisplayed ? (null) : ( - <div className="mainViewModal-cont" style={{ - pointerEvents: p.isDisplayed && p.interactive ? "all" : "none" - }}> - <div className="dialogue-box" style={{ - borderColor: "black", - ...(p.dialogueBoxStyle || {}), - opacity: p.isDisplayed ? dialogueOpacity : 0 - }} > + return !p.isDisplayed ? null : ( + <div + className="mainViewModal-cont" + style={{ + pointerEvents: p.isDisplayed && p.interactive ? 'all' : 'none', + }}> + <div + className="dialogue-box" + style={{ + borderColor: 'black', + height: 'max-content', + overflow: 'auto', + maxHeight: '80%', + ...(p.dialogueBoxStyle || {}), + opacity: p.isDisplayed ? dialogueOpacity : 0, + }}> {p.contents} </div> - <div className="overlay" + <div + className="overlay" onClick={this.props?.closeOnExternalClick} style={{ - backgroundColor: "black", + backgroundColor: 'black', ...(p.overlayStyle || {}), - opacity: p.isDisplayed ? overlayOpacity : 0 + opacity: p.isDisplayed ? overlayOpacity : 0, }} /> </div> ); } -}
\ No newline at end of file +} diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index ede387927..8fd2b87cc 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -206,7 +206,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> { return textRegionAnno; }; @action - highlight = (color: string, isLinkButton: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => { + highlight = (color: string, isLinkButton: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean, summarize?: boolean) => { // creates annotation documents for current highlights const effectiveAcl = GetEffectiveAcl(this.props.rootDoc[DataSym]); const annotationDoc = [AclAugment, AclSelfEdit, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color, isLinkButton, savedAnnotations); diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index bdc48d03a..9baedae6d 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -7,14 +7,14 @@ import { Doc, DocListCast, HeightSym, WidthSym } from '../../fields/Doc'; import { Id } from '../../fields/FieldSymbols'; import { NumCast } from '../../fields/Types'; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../Utils'; -import { DocUtils } from '../documents/Documents'; import { DocumentType } from '../documents/DocumentTypes'; +import { DocumentManager } from '../util/DocumentManager'; import { DragManager } from '../util/DragManager'; import { Transform } from '../util/Transform'; import { CollectionFreeFormLinksView } from './collections/collectionFreeForm/CollectionFreeFormLinksView'; import { LightboxView } from './LightboxView'; import { MainView } from './MainView'; -import { DocumentView } from './nodes/DocumentView'; +import { DocumentView, DocumentViewInternal } from './nodes/DocumentView'; import './OverlayView.scss'; import { DefaultStyleProvider } from './StyleProvider'; @@ -224,7 +224,7 @@ export class OverlayView extends React.Component { focus={emptyFunction} styleProvider={DefaultStyleProvider} docViewPath={returnEmptyDoclist} - addDocTab={d.type === DocumentType.PRES ? MainView.addDocTabFunc : returnFalse} + addDocTab={DocumentViewInternal.addDocTabFunc} pinToPres={emptyFunction} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx index 95ae65d7a..c7a603897 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -19,16 +19,7 @@ export class PreviewCursor extends React.Component<{}> { static _addDocument: (doc: Doc | Doc[]) => boolean; static _addLiveTextDoc: (doc: Doc) => void; static _nudge?: undefined | ((x: number, y: number) => boolean); - static _slowLoadDocuments?: ( - files: File[] | string, - options: DocumentOptions, - generatedDocuments: Doc[], - text: string, - completed: ((doc: Doc[]) => void) | undefined, - clientX: number, - clientY: number, - addDocument: (doc: Doc | Doc[]) => boolean - ) => Promise<void>; + static _slowLoadDocuments?: (files: File[] | string, options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: ((doc: Doc[]) => void) | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => Promise<void>; @observable static _clickPoint = [0, 0]; @observable public static Visible = false; constructor(props: any) { @@ -57,7 +48,7 @@ export class PreviewCursor extends React.Component<{}> { x: newPoint[0], y: newPoint[1], }; - PreviewCursor._slowLoadDocuments?.(plain.split('v=')[1].split('&')[0], options, generatedDocuments, '', undefined, newPoint[0], newPoint[1], PreviewCursor._addDocument).then(batch.end); + PreviewCursor._slowLoadDocuments?.(plain.split('v=')[1].split('&')[0], options, generatedDocuments, '', undefined, PreviewCursor._addDocument).then(batch.end); } else if (re.test(plain)) { const url = plain; undoBatch(() => @@ -185,16 +176,7 @@ export class PreviewCursor extends React.Component<{}> { getTransform: () => Transform, addDocument: undefined | ((doc: Doc | Doc[]) => boolean), nudge: undefined | ((nudgeX: number, nudgeY: number) => boolean), - slowLoadDocuments: ( - files: File[] | string, - options: DocumentOptions, - generatedDocuments: Doc[], - text: string, - completed: ((doc: Doc[]) => void) | undefined, - clientX: number, - clientY: number, - addDocument: (doc: Doc | Doc[]) => boolean - ) => Promise<void> + slowLoadDocuments: (files: File[] | string, options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: ((doc: Doc[]) => void) | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => Promise<void> ) { this._clickPoint = [x, y]; this._onKeyPress = onKeyPress; diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index 031d501ad..cf808f801 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -136,10 +136,11 @@ export class PropertiesButtons extends React.Component<{}, {}> { // containerDoc.noShadow = // containerDoc.disableDocBrushing = // containerDoc._forceActive = - containerDoc._fitContentsToBox = containerDoc._isLightbox = !containerDoc._isLightbox; - containerDoc._xPadding = containerDoc._yPadding = containerDoc._isLightbox ? 10 : undefined; + //containerDoc._fitContentsToBox = + containerDoc._isLightbox = !containerDoc._isLightbox; + //containerDoc._xPadding = containerDoc._yPadding = containerDoc._isLightbox ? 10 : undefined; const containerContents = DocListCast(dv.dataDoc[dv.props.fieldKey ?? Doc.LayoutFieldKey(containerDoc)]); - dv.rootDoc.onClick = ScriptField.MakeScript('{self.data = undefined; documentView.select(false)}', { documentView: 'any' }); + //dv.rootDoc.onClick = ScriptField.MakeScript('{self.data = undefined; documentView.select(false)}', { documentView: 'any' }); containerContents.forEach(doc => LinkManager.Links(doc).forEach(link => (link.linkDisplay = false))); }); } @@ -177,7 +178,11 @@ export class PropertiesButtons extends React.Component<{}, {}> { '_showTitle', on => 'Switch between title styles', on => 'text-width', - (dv, doc) => ((dv?.rootDoc || doc)._showTitle = !(dv?.rootDoc || doc)._showTitle ? 'title' : (dv?.rootDoc || doc)._showTitle === 'title' ? 'title:hover' : undefined) + (dv, doc) => { + const tdoc = dv?.rootDoc || doc; + const newtitle = !tdoc._showTitle ? 'title' : tdoc._showTitle === 'title' ? 'title:hover' : ''; + tdoc._showTitle = newtitle; + } ); } @computed get autoHeightButton() { diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 5f16e0ebd..d13052f71 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -24,13 +24,13 @@ import { KeyValueBox } from './nodes/KeyValueBox'; import { SliderBox } from './nodes/SliderBox'; import './StyleProvider.scss'; import React = require('react'); +import { SchemaRowBox } from './collections/collectionSchema/SchemaRowBox'; export enum StyleProp { TreeViewIcon = 'treeViewIcon', TreeViewSortings = 'treeViewSortings', // options for how to sort tree view items DocContents = 'docContents', // when specified, the JSX returned will replace the normal rendering of the document view Opacity = 'opacity', // opacity of the document view - Hidden = 'hidden', // whether the document view should not be isplayed BoxShadow = 'boxShadow', // box shadow - used for making collections standout and for showing clusters in free form views BorderRounding = 'borderRounding', // border radius of the document view Color = 'color', // foreground color of Document view items @@ -172,8 +172,6 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps const backColor = backgroundCol() || docView?.props.styleProvider?.(docView.props.treeViewDoc, docView.props, StyleProp.BackgroundColor); if (!backColor) return undefined; return lightOrDark(backColor); - case StyleProp.Hidden: - return props?.LayoutTemplateString?.includes(KeyValueBox.name) ? false : BoolCast(doc?.hidden); case StyleProp.BorderRounding: return StrCast(doc?.[fieldKey + 'borderRounding'], StrCast(doc?.borderRounding, doc?._viewType === CollectionViewType.Pile ? '50%' : '')); case StyleProp.TitleHeight: @@ -257,7 +255,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps if (StrCast(Doc.LayoutField(doc)).includes(SliderBox.name)) break; docColor = docColor || - (doc?._viewType === CollectionViewType.Pile || Doc.IsSystem(doc) + (Doc.IsSystem(doc) ? darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY // system docs (seen in treeView) get a grayish background diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx index fd9b0c0ce..5b96a8682 100644 --- a/src/client/views/collections/CollectionPileView.tsx +++ b/src/client/views/collections/CollectionPileView.tsx @@ -1,6 +1,6 @@ import { action, computed, IReactionDisposer, reaction } from 'mobx'; import { observer } from 'mobx-react'; -import { Doc, HeightSym, WidthSym } from '../../../fields/Doc'; +import { Doc, DocListCast, HeightSym, WidthSym } from '../../../fields/Doc'; import { NumCast, StrCast } from '../../../fields/Types'; import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils'; import { DocUtils } from '../../documents/Documents'; @@ -26,12 +26,6 @@ export class CollectionPileView extends CollectionSubView() { } this._originalChrome = this.layoutDoc._chromeHidden; this.layoutDoc._chromeHidden = true; - - // pileups are designed to go away when they are empty. - this._disposers.selected = reaction( - () => this.childDocs.length, - num => !num && this.props.CollectionFreeFormDocumentView?.().props.removeDocument?.(this.props.Document) - ); } componentWillUnmount() { this.layoutDoc._chromeHidden = this._originalChrome; @@ -48,13 +42,15 @@ export class CollectionPileView extends CollectionSubView() { @undoBatch removePileDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => { - (doc instanceof Doc ? [doc] : doc).map(undoBatch(d => Doc.deiconifyView(d))); - return this.props.moveDocument?.(doc, targetCollection, addDoc) || false; + (doc instanceof Doc ? [doc] : doc).forEach(d => Doc.deiconifyView(d)); + const ret = this.props.moveDocument?.(doc, targetCollection, addDoc) || false; + if (ret && !DocListCast(this.rootDoc[this.fieldKey ?? 'data']).length) this.props.DocumentView?.().props.removeDocument?.(this.rootDoc); + return ret; }; - toggleIcon = () => { + @computed get toggleIcon() { return ScriptField.MakeScript('documentView.iconify()', { documentView: 'any' }); - }; + } // returns the contents of the pileup in a CollectionFreeFormView @computed get contents() { @@ -63,11 +59,11 @@ export class CollectionPileView extends CollectionSubView() { <div className="collectionPileView-innards" style={{ pointerEvents: isStarburst || SnappingManager.GetIsDragging() ? undefined : 'none' }}> <CollectionFreeFormView {...this.props} + childContentsActive={returnFalse} layoutEngine={this.layoutEngine} - childDocumentsActive={isStarburst ? returnTrue : undefined} addDocument={this.addPileDoc} childCanEmbedOnDrag={true} - childClickScript={this.toggleIcon()} + childClickScript={this.toggleIcon} moveDocument={this.removePileDoc} /> </div> @@ -77,6 +73,9 @@ export class CollectionPileView extends CollectionSubView() { // toggles the pileup between starburst to compact toggleStarburst = action(() => { if (this.layoutEngine() === computeStarburstLayout.name) { + if (this.rootDoc[WidthSym]() !== NumCast(this.rootDoc._starburstDiameter, 500)) { + this.rootDoc._starburstDiameter = this.rootDoc[WidthSym](); + } const defaultSize = 110; this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - NumCast(this.layoutDoc._starburstPileWidth, defaultSize) / 2; this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - NumCast(this.layoutDoc._starburstPileHeight, defaultSize) / 2; @@ -87,15 +86,11 @@ export class CollectionPileView extends CollectionSubView() { this.layoutDoc._panY = -10; this.props.Document._pileLayoutEngine = computePassLayout.name; } else { - const defaultSize = 25; - !this.layoutDoc._starburstRadius && (this.layoutDoc._starburstRadius = 250); - !this.layoutDoc._starburstDocScale && (this.layoutDoc._starburstDocScale = 2.5); - if (this.layoutEngine() === computePassLayout.name) { - this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - defaultSize / 2; - this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - defaultSize / 2; - this.layoutDoc._starburstPileWidth = this.layoutDoc[WidthSym](); - this.layoutDoc._starburstPileHeight = this.layoutDoc[HeightSym](); - } + const defaultSize = NumCast(this.rootDoc._starburstDiameter, 500); + this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - defaultSize / 2; + this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - defaultSize / 2; + this.layoutDoc._starburstPileWidth = this.layoutDoc[WidthSym](); + this.layoutDoc._starburstPileHeight = this.layoutDoc[HeightSym](); this.layoutDoc._panX = this.layoutDoc._panY = 0; this.layoutDoc._width = this.layoutDoc._height = defaultSize; this.props.Document._pileLayoutEngine = computeStarburstLayout.name; diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index bdad325d5..eedf639aa 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -10,7 +10,7 @@ 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, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils'; +import { emptyFunction, returnEmptyDoclist, returnFalse, returnNone, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { CollectionViewType } from '../../documents/DocumentTypes'; import { DragManager, dropActionType } from '../../util/DragManager'; @@ -238,9 +238,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection this.createDashEventsTarget(ele!); //so the whole grid is the drop target? }; - @computed get onChildClickHandler() { - return () => this.props.childClickScript || ScriptCast(this.Document.onChildClick); - } + onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick); @computed get onChildDoubleClickHandler() { return () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); } @@ -300,7 +298,13 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection @observable _renderCount = 5; isChildContentActive = () => - this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive)) ? true : this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false ? false : undefined; + this.props.isContentActive?.() === false + ? false + : this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive)) + ? true + : this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false + ? false + : undefined; isChildButtonContentActive = () => (this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false ? false : undefined); // this is what renders the document that you see on the screen // called in Children: this actually adds a document to our children list @@ -320,6 +324,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection renderDepth={this.props.renderDepth + 1} PanelWidth={width} PanelHeight={height} + pointerEvents={this.props.DocumentView?.().props.onClick?.() ? returnNone : undefined} // if the stack has an onClick, then we don't want the contents to be interactive (see CollectionPileView) styleProvider={this.styleProvider} docViewPath={this.props.docViewPath} fitWidth={this.props.childFitWidth} diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 5581ac8fe..5b9453666 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -110,9 +110,7 @@ export function CollectionSubView<X>(moreProps?: X) { rawdocs = rootDoc && !this.props.isAnnotationOverlay ? [Doc.GetProto(rootDoc)] : []; } - const docs = rawdocs.filter(d => !(d instanceof Promise) && GetEffectiveAcl(Doc.GetProto(d)) !== AclPrivate && (this.props.ignoreUnrendered || !d.unrendered)).map(d => d as Doc); - const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField); - const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs; + const childDocs = rawdocs.filter(d => !(d instanceof Promise) && GetEffectiveAcl(Doc.GetProto(d)) !== AclPrivate && (this.props.ignoreUnrendered || !d.unrendered)).map(d => d as Doc); const childDocFilters = this.childDocFilters(); const docRangeFilters = this.childDocRangeFilters(); @@ -126,24 +124,23 @@ export function CollectionSubView<X>(moreProps?: X) { // dragging facets const dragged = this.props.docFilters?.().some(f => f.includes(Utils.noDragsDocFilter)); if (dragged && DragManager.docsBeingDragged.includes(d)) return false; - let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), docRangeFilters, viewSpecScript, this.props.Document).length > 0; + let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), docRangeFilters, this.props.Document).length > 0; if (notFiltered) { - notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, docRangeFilters, viewSpecScript, this.props.Document).length > 0; + notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, docRangeFilters, this.props.Document).length > 0; const fieldKey = Doc.LayoutFieldKey(d); - const annos = !Field.toString(Doc.LayoutField(d) as Field).includes('CollectionView'); + const annos = !Field.toString(Doc.LayoutField(d) as Field).includes(CollectionView.name); const data = d[annos ? fieldKey + '-annotations' : fieldKey]; if (data !== undefined) { let subDocs = DocListCast(data); if (subDocs.length > 0) { let newarray: Doc[] = []; - notFiltered = notFiltered || (!searchDocs.length && DocUtils.FilterDocs(subDocs, childDocFilters, docRangeFilters, viewSpecScript, d).length); + notFiltered = notFiltered || (!searchDocs.length && DocUtils.FilterDocs(subDocs, childDocFilters, docRangeFilters, d).length); while (subDocs.length > 0 && !notFiltered) { newarray = []; subDocs.forEach(t => { const fieldKey = Doc.LayoutFieldKey(t); - const annos = !Field.toString(Doc.LayoutField(t) as Field).includes('CollectionView'); - notFiltered = - notFiltered || ((!searchDocs.length || searchDocs.includes(t)) && ((!childDocFilters.length && !docRangeFilters.length) || DocUtils.FilterDocs([t], childDocFilters, docRangeFilters, viewSpecScript, d).length)); + const annos = !Field.toString(Doc.LayoutField(t) as Field).includes(CollectionView.name); + notFiltered = notFiltered || ((!searchDocs.length || searchDocs.includes(t)) && ((!childDocFilters.length && !docRangeFilters.length) || DocUtils.FilterDocs([t], childDocFilters, docRangeFilters, d).length)); DocListCast(t[annos ? fieldKey + '-annotations' : fieldKey]).forEach(newdoc => newarray.push(newdoc)); }); subDocs = newarray; @@ -348,7 +345,7 @@ export function CollectionSubView<X>(moreProps?: X) { if ((uriList || text).includes('www.youtube.com/watch') || text.includes('www.youtube.com/embed')) { const batch = UndoManager.StartBatch('youtube upload'); const generatedDocuments: Doc[] = []; - this.slowLoadDocuments((uriList || text).split('v=')[1].split('&')[0], options, generatedDocuments, text, completed, e.clientX, e.clientY, addDocument).then(batch.end); + this.slowLoadDocuments((uriList || text).split('v=')[1].split('&')[0], options, generatedDocuments, text, completed, addDocument).then(batch.end); return; } @@ -370,18 +367,8 @@ export function CollectionSubView<X>(moreProps?: X) { // } } if (uriList) { - // const existingWebDoc = await Hypothesis.findWebDoc(uriList); - // if (existingWebDoc) { - // const alias = Doc.MakeAlias(existingWebDoc); - // alias.x = options.x; - // alias.y = options.y; - // alias._nativeWidth = 850; - // alias._height = 512; - // alias._width = 400; - // addDocument(alias); - // } else - { - const newDoc = Docs.Create.WebDocument(uriList.split('#annotations:')[0], { + addDocument( + Docs.Create.WebDocument(uriList.split('#annotations:')[0], { // clean hypothes.is URLs that reference a specific annotation (eg. https://en.wikipedia.org/wiki/Cartoon#annotations:t7qAeNbCEeqfG5972KR2Ig) ...options, title: uriList.split('#annotations:')[0], @@ -389,9 +376,8 @@ export function CollectionSubView<X>(moreProps?: X) { _height: 512, _nativeWidth: 850, useCors: true, - }); - addDocument(newDoc); - } + }) + ); return; } @@ -437,19 +423,10 @@ export function CollectionSubView<X>(moreProps?: X) { }); } } - this.slowLoadDocuments(files, options, generatedDocuments, text, completed, e.clientX, e.clientY, addDocument).then(batch.end); + this.slowLoadDocuments(files, options, generatedDocuments, text, completed, addDocument).then(batch.end); } - slowLoadDocuments = async ( - files: File[] | string, - options: DocumentOptions, - generatedDocuments: Doc[], - text: string, - completed: ((doc: Doc[]) => void) | undefined, - clientX: number, - clientY: number, - addDocument: (doc: Doc | Doc[]) => boolean - ) => { + slowLoadDocuments = async (files: File[] | string, options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: ((doc: Doc[]) => void) | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => { // create placeholder docs // inside placeholder docs have some func that diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index 4d5978548..c3f77205a 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -7,22 +7,21 @@ import { ObjectField } from '../../../fields/ObjectField'; import { RichTextField } from '../../../fields/RichTextField'; import { listSpec } from '../../../fields/Schema'; import { ComputedField, ScriptField } from '../../../fields/ScriptField'; -import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types'; +import { Cast, NumCast, StrCast } from '../../../fields/Types'; import { emptyFunction, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils'; import { Docs } from '../../documents/Documents'; -import { DocumentType } from '../../documents/DocumentTypes'; import { DocumentManager } from '../../util/DocumentManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { EditableView } from '../EditableView'; +import { DocFocusOptions, DocumentView } from '../nodes/DocumentView'; +import { PresBox } from '../nodes/trails'; import { computePivotLayout, computeTimelineLayout, ViewDefBounds } from './collectionFreeForm/CollectionFreeFormLayoutEngines'; import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; import { CollectionSubView } from './CollectionSubView'; import './CollectionTimeView.scss'; import React = require('react'); -import { DocFocusOptions, DocumentView } from '../nodes/DocumentView'; -import { PresBox } from '../nodes/trails'; @observer export class CollectionTimeView extends CollectionSubView() { @@ -141,7 +140,6 @@ export class CollectionTimeView extends CollectionSubView() { } }; - dontScaleFilter = (doc: Doc) => doc.type === DocumentType.RTF; @computed get contents() { return ( <div className="collectionTimeView-innards" key="timeline" style={{ pointerEvents: this.props.isContentActive() ? undefined : 'none' }} onClick={this.contentsDown}> @@ -151,7 +149,6 @@ export class CollectionTimeView extends CollectionSubView() { fitContentsToBox={returnTrue} childClickScript={this._childClickedScript} viewDefDivClick={this.layoutEngine() === computeTimelineLayout.name ? undefined : this._viewDefDivClick} - //dontScaleFilter={this.dontScaleFilter} layoutEngine={this.layoutEngine} /> </div> @@ -277,7 +274,7 @@ export class CollectionTimeView extends CollectionSubView() { } ScriptingGlobals.add(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBounds) { - const pivotField = StrCast(pivotDoc._pivotField) || 'author'; + const pivotField = StrCast(pivotDoc._pivotField, 'author'); let prevFilterIndex = NumCast(pivotDoc._prevFilterIndex); const originalFilter = StrListCast(ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField)); pivotDoc['_prevDocFilter' + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField); diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index cfb9310b6..b76033a0c 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -46,6 +46,7 @@ interface CollectionViewProps_ extends FieldViewProps { // property overrides for child documents childDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explicit list (see LinkBox) childDocumentsActive?: () => boolean | undefined; // whether child documents can be dragged if collection can be dragged (eg., in a when a Pile document is in startburst mode) + childContentsActive?: () => boolean | undefined; childFitWidth?: (child: Doc) => boolean; childShowTitle?: () => string; childOpacity?: () => number; @@ -156,12 +157,13 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab const cm = ContextMenu.Instance; if (cm && !e.isPropagationStopped()) { // 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, OpenWhere.addRight); - return newRendition; - }); + !Doc.noviceMode && + this.setupViewTypes('UI Controls...', vtype => { + const newRendition = Doc.MakeAlias(this.rootDoc); + newRendition._viewType = vtype; + this.props.addDocTab(newRendition, OpenWhere.addRight); + return newRendition; + }); const options = cm.findByDescription('Options...'); const optionItems = options && 'subitems' in options ? options.subitems : []; diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 4bbc3bb44..45604c1bf 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -266,6 +266,7 @@ export class TabDocView extends React.Component<TabDocViewProps> { pinDoc.treeViewHideHeaderIfTemplate = true; // this will force the document to render itself as the tree view header const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}-duration`], null); + if (pinProps.pinViewport) PresBox.pinDocView(pinDoc, pinProps, anchorDoc ?? doc); if (!pinProps?.audioRange && duration !== undefined) { pinDoc.mediaStart = 'manual'; pinDoc.mediaStop = 'manual'; diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 8fb610b87..4adf86683 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -801,7 +801,6 @@ export class TreeView extends React.Component<TreeViewProps> { case StyleProp.Opacity: return this.props.treeView.outlineMode ? undefined : 1; case StyleProp.BackgroundColor: return this.selected ? '#7089bb' : StrCast(doc._backgroundColor, StrCast(doc.backgroundColor)); case StyleProp.Highlighting: if (this.props.treeView.outlineMode) return undefined; - case StyleProp.Hidden: return false; case StyleProp.BoxShadow: return undefined; case StyleProp.DocContents: const highlightIndex = this.props.treeView.outlineMode ? Doc.DocBrushStatus.unbrushed : Doc.isBrushedHighlightedDegree(doc); @@ -827,7 +826,6 @@ export class TreeView extends React.Component<TreeViewProps> { }; embeddedStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => { if (property.startsWith(StyleProp.Decorations)) return null; - if (property.startsWith(StyleProp.Hidden)) return false; return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView }; onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index 81b0c4d8a..c1f3c5aa6 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -93,6 +93,7 @@ export function computePassLayout(poolData: Map<string, PoolData>, pivotDoc: Doc width: layout[WidthSym](), height: layout[HeightSym](), pair: { layout, data }, + transition: 'all .3s', replica: '', }); }); @@ -100,28 +101,28 @@ export function computePassLayout(poolData: Map<string, PoolData>, pivotDoc: Doc } export function computeStarburstLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) { - const mustFit = pivotDoc[WidthSym]() !== panelDim[0]; // if a panel size is set that's not the same as the pivot doc's size, then assume this is in a panel for a content fitting view (like a grid) in which case everything must be scaled to stay within the panel const docMap = new Map<string, PoolData>(); - const docSize = mustFit ? panelDim[0] * 0.33 : 75; // assume an icon sized at 75 - const burstRadius = mustFit ? panelDim : [NumCast(pivotDoc._starburstRadius, panelDim[0]) - docSize, NumCast(pivotDoc._starburstRadius, panelDim[1]) - docSize]; - const scaleDim = [burstRadius[0] * 2 + docSize, burstRadius[1] * 2 + docSize]; + const burstDiam = [NumCast(pivotDoc._width), NumCast(pivotDoc._height)]; + const burstScale = NumCast(pivotDoc._starburstDocScale, 1); childPairs.forEach(({ layout, data }, i) => { - const docSize = layout.layoutKey === 'layout_icon' ? (mustFit ? panelDim[0] * 0.33 : 75) : 400; // assume a icon sized at 75 + const aspect = layout[HeightSym]() / layout[WidthSym](); + const docSize = Math.min(Math.min(400, layout[WidthSym]()), Math.min(400, layout[WidthSym]()) / aspect) * burstScale; const deg = (i / childPairs.length) * Math.PI * 2; docMap.set(layout[Id], { - x: Math.cos(deg) * burstRadius[0] - docSize / 2, - y: Math.sin(deg) * burstRadius[1] - (docSize * layout[HeightSym]()) / layout[WidthSym]() / 2, - width: docSize, //layout[WidthSym](), - height: (docSize * layout[HeightSym]()) / layout[WidthSym](), + x: Math.min(burstDiam[0] / 2 - docSize, Math.max(-burstDiam[0] / 2, (Math.cos(deg) * burstDiam[0]) / 2 - docSize / 2)), + y: Math.min(burstDiam[1] / 2 - docSize * aspect, Math.max(-burstDiam[1] / 2, (Math.sin(deg) * burstDiam[1]) / 2 - (docSize / 2) * aspect)), + width: docSize, + height: docSize * aspect, zIndex: NumCast(layout.zIndex), pair: { layout, data }, replica: '', color: 'white', backgroundColor: 'white', + transition: 'all 0.3s', }); }); - const divider = { type: 'div', color: 'transparent', x: -burstRadius[0], y: 0, width: 15, height: 15, payload: undefined }; - return normalizeResults(scaleDim, 12, docMap, poolData, viewDefsToJSX, [], 0, [divider]); + const divider = { type: 'div', color: 'transparent', x: -burstDiam[0] / 2, y: -burstDiam[1] / 2, width: 15, height: 15, payload: undefined }; + return normalizeResults(burstDiam, 12, docMap, poolData, viewDefsToJSX, [], 0, [divider]); } export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) { @@ -424,6 +425,7 @@ function normalizeResults( color: newPosRaw.color, pair: ele[1].pair, }; + if (newPosRaw.transition) newPos.transition = newPosRaw.transition; poolData.set(newPos.pair.layout[Id] + (newPos.replica || ''), { transition: 'all 1s', ...newPos }); } }); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 33fc2ddf3..9cc732008 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -15,7 +15,7 @@ import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } fro import { ImageField } from '../../../../fields/URLField'; import { TraceMobx } from '../../../../fields/util'; import { GestureUtils } from '../../../../pen-gestures/GestureUtils'; -import { aggregateBounds, emptyFunction, intersectRect, returnFalse, setupMoveUpEvents, Utils } from '../../../../Utils'; +import { aggregateBounds, emptyFunction, intersectRect, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils'; import { CognitiveServices } from '../../../cognitive_services/CognitiveServices'; import { Docs, DocUtils } from '../../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; @@ -64,7 +64,6 @@ export type collectionFreeformViewProps = { noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale) engineProps?: any; getScrollHeight?: () => number | undefined; - dontScaleFilter?: (doc: Doc) => boolean; // whether this collection should scale documents to fit their panel vs just scrolling them dontRenderDocuments?: boolean; // used for annotation overlays which need to distribute documents into different freeformviews with different mixBlendModes depending on whether they are transparent or not. // However, this screws up interactions since only the top layer gets events. so we render the freeformview a 3rd time with all documents in order to get interaction events (eg., marquee) but we don't actually want to display the documents. }; @@ -141,7 +140,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @computed get fitToContentVals() { return { bounds: { ...this.contentBounds, cx: (this.contentBounds.x + this.contentBounds.r) / 2, cy: (this.contentBounds.y + this.contentBounds.b) / 2 }, - scale: !this.childDocs.length ? 1 : Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y), this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)), + scale: + !this.childDocs.length || !Number.isFinite(this.contentBounds.b - this.contentBounds.y) || !Number.isFinite(this.contentBounds.r - this.contentBounds.x) + ? 1 + : Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y), this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)), }; } @computed get fitContentsToBox() { @@ -308,6 +310,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection }; focus = (anchor: Doc, options: DocFocusOptions) => { + if (this._lightboxDoc) return; const xfToCollection = options?.docTransform ?? Transform.Identity(); const savedState = { panX: NumCast(this.Document[this.panXFieldKey]), panY: NumCast(this.Document[this.panYFieldKey]), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined }; const cantTransform = this.fitContentsToBox || ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc); @@ -327,7 +330,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection getView = async (doc: Doc): Promise<Opt<DocumentView>> => { return new Promise<Opt<DocumentView>>(res => { - doc.hidden && (doc.hidden = false); + if (doc.hidden && this._lightboxDoc !== doc) doc.hidden = false; const findDoc = (finish: (dv: DocumentView) => void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv)); findDoc(dv => res(dv)); }); @@ -565,7 +568,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection switch (property) { case StyleProp.BackgroundColor: const cluster = NumCast(doc?.cluster); - if (this.Document._useClusters) { + if (this.Document._useClusters && doc?.type !== DocumentType.IMG) { if (this._clusterSets.length <= cluster) { setTimeout(() => doc && this.updateCluster(doc)); } else { @@ -778,7 +781,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this._batch?.end(); }; + @action onClick = (e: React.MouseEvent) => { + if (this._lightboxDoc) this._lightboxDoc = undefined; if (this.onBrowseClickHandler()) { if (this.props.DocumentView?.()) { this.onBrowseClickHandler().script.run({ documentView: this.props.DocumentView(), clientX: e.clientX, clientY: e.clientY }); @@ -786,7 +791,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection e.stopPropagation(); e.preventDefault(); } else if (Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3) { - if (e.shiftKey) { + if (e.shiftKey && (this.props.renderDepth === 0 || this.isContentActive())) { if (Date.now() - this._lastTap < 300) { // reset zoom of freeform view to 1-to-1 on a shift + double click this.zoomSmoothlyAboutPt(this.getTransform().transformPoint(e.clientX, e.clientY), 1); @@ -816,6 +821,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this._lastY = e.clientY; }; + _eraserLock = 0; /** * Erases strokes by intersecting them with an invisible "eraser stroke". * By default this iterates through all intersected ink strokes, determines their segmentation, draws back the non-intersected segments, @@ -825,6 +831,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @action onEraserMove = (e: PointerEvent, down: number[], delta: number[]) => { const currPoint = { X: e.clientX, Y: e.clientY }; + if (this._eraserLock) return false; // bcz: should be fixed by putting it on a queue to be processed after the last eraser movement is processed. this.getEraserIntersections({ X: currPoint.X - delta[0], Y: currPoint.Y - delta[1] }, currPoint).forEach(intersect => { if (!this._deleteList.includes(intersect.inkView)) { this._deleteList.push(intersect.inkView); @@ -832,12 +839,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection SetActiveInkColor(StrCast(intersect.inkView.rootDoc.color?.toString()) || 'black'); // create a new curve by appending all curves of the current segment together in order to render a single new stroke. if (!e.shiftKey) { + this._eraserLock++; this.segmentInkStroke(intersect.inkView, intersect.t).forEach(segment => - GestureOverlay.Instance.dispatchGesture( + this.forceStrokeGesture( + e, GestureUtils.Gestures.Stroke, segment.reduce((data, curve) => [...data, ...curve.points.map(p => intersect.inkView.ComponentView?.ptToScreen?.({ X: p.x, Y: p.y }) ?? { X: 0, Y: 0 })], [] as PointData[]) ) ); + setTimeout(() => this._eraserLock--); } // Lower ink opacity to give the user a visual indicator of deletion. intersect.inkView.layoutDoc.opacity = 0.5; @@ -846,6 +856,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection }); return false; }; + forceStrokeGesture = (e: PointerEvent, gesture: GestureUtils.Gestures, points: InkData, text?: any) => { + this.onGesture(e, new GestureUtils.GestureEvent(gesture, points, GestureOverlay.getBounds(points), text)); + }; @action onPointerMove = (e: PointerEvent): boolean => { @@ -924,7 +937,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const docCurveTVal = t + Math.floor(i / 4); if (excludeT < startSegmentT || excludeT > docCurveTVal) { const localStartTVal = startSegmentT - Math.floor(i / 4); - segment.push(inkSegment.split(localStartTVal < 0 ? 0 : localStartTVal, t)); + t !== (localStartTVal < 0 ? 0 : localStartTVal) && segment.push(inkSegment.split(localStartTVal < 0 ? 0 : localStartTVal, t)); segment.length && segments.push(segment); } // start a new segment from the intersection t value @@ -974,11 +987,21 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection if (ink?.Document === otherInk.props.Document && neighboringSegment) continue; const otherCurve = new Bezier(otherCtrlPts.slice(j, j + 4).map(p => ({ x: p.X, y: p.Y }))); + const c0 = otherCurve.get(0); + const c1 = otherCurve.get(1); + const apt = curve.project(c0); + const bpt = curve.project(c1); + if (apt.d !== undefined && apt.d < 1 && apt.t !== undefined && !tVals.includes(apt.t)) { + tVals.push(apt.t); + } this.bintersects(curve, otherCurve).forEach((val: string | number, i: number) => { // Converting the Bezier.js Split type to a t-value number. const t = +val.toString().split('/')[0]; if (i % 2 === 0 && !tVals.includes(t)) tVals.push(t); // bcz: Hack! don't know why but intersection points are doubled from bezier.js (but not identical). }); + if (bpt.d !== undefined && bpt.d < 1 && bpt.t !== undefined && !tVals.includes(bpt.t)) { + tVals.push(bpt.t); + } } }); return tVals; @@ -1274,7 +1297,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection docRangeFilters={this.childDocRangeFilters} searchFilterDocs={this.searchFilterDocs} isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive} - isContentActive={emptyFunction} + isContentActive={this.props.childContentsActive ?? emptyFunction} focus={this.Document._isGroup ? this.groupFocus : this.isAnnotationOverlay ? this.props.focus : this.focus} addDocTab={this.addDocTab} addDocument={this.props.addDocument} @@ -1290,7 +1313,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection dropAction={StrCast(this.props.Document.childDropAction) as dropActionType} bringToFront={this.bringToFront} showTitle={this.props.childShowTitle} - dontScaleFilter={this.props.dontScaleFilter} dontRegisterView={this.props.dontRenderDocuments || this.props.dontRegisterView} pointerEvents={this.pointerEvents} //rotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0} @@ -1320,13 +1342,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection case undefined: case OpenWhere.lightbox: if (this.layoutDoc._isLightbox) { - // _isLightbox docs have a script that will unset this overlay onClick - this.layoutDoc[this.props.fieldKey] = new List<Doc>(doc instanceof Doc ? [doc] : doc); + this._lightboxDoc = doc; + return true; + } + if (this.childDocList?.includes(doc)) { + if (doc.hidden) doc.hidden = false; return true; } } return this.props.addDocTab(doc, where); }); + @observable _lightboxDoc: Opt<Doc>; getCalculatedPositions(params: { pair: { layout: Doc; data?: Doc }; index: number; collection: Doc }): PoolData { const childDoc = params.pair.layout; @@ -1936,7 +1962,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection 1000 ); }; - + lightboxPanelWidth = () => Math.max(0, this.props.PanelWidth() - 30); + lightboxPanelHeight = () => Math.max(0, this.props.PanelHeight() - 30); + lightboxScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-15, -15); render() { TraceMobx(); return ( @@ -1961,40 +1989,70 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection : SnappingManager.GetIsDragging() && this.childDocs.includes(DragManager.docsBeingDragged.lastElement()) ? 'all' : (this.props.pointerEvents?.() as any), + textAlign: this.isAnnotationOverlay ? 'initial' : undefined, transform: `scale(${this.nativeDimScaling || 1})`, width: `${100 / (this.nativeDimScaling || 1)}%`, height: this.props.getScrollHeight?.() ?? `${100 / (this.nativeDimScaling || 1)}%`, }}> - {this._firstRender ? this.placeholder : this.marqueeView} - {this.props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />} - - {/* // uncomment to show snap lines */} - <div className="snapLines" style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', pointerEvents: 'none' }}> - <svg style={{ width: '100%', height: '100%' }}> - {this._hLines?.map(l => ( - <line x1="0" y1={l} x2="1000" y2={l} stroke="black" /> - ))} - {this._vLines?.map(l => ( - <line y1="0" x1={l} y2="1000" x2={l} stroke="black" /> - ))} - </svg> - </div> + {this._lightboxDoc ? ( + <div style={{ padding: 15, width: '100%', height: '100%' }}> + <DocumentView + {...this.props} + Document={this._lightboxDoc} + DataDoc={undefined} + PanelWidth={this.lightboxPanelWidth} + PanelHeight={this.lightboxPanelHeight} + NativeWidth={returnZero} + NativeHeight={returnZero} + onClick={this.onChildClickHandler} + onKey={this.onKeyDown} + onDoubleClick={this.onChildDoubleClickHandler} + onBrowseClick={this.onBrowseClickHandler} + docFilters={this.childDocFilters} + docRangeFilters={this.childDocRangeFilters} + searchFilterDocs={this.searchFilterDocs} + isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive} + isContentActive={this.props.childContentsActive ?? emptyFunction} + addDocTab={this.addDocTab} + ScreenToLocalTransform={this.lightboxScreenToLocal} + fitContentsToBox={undefined} + focus={this.focus} + /> + </div> + ) : ( + <> + {this._firstRender ? this.placeholder : this.marqueeView} + {this.props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />} + + {/* // uncomment to show snap lines */} + <div className="snapLines" style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', pointerEvents: 'none' }}> + <svg style={{ width: '100%', height: '100%' }}> + {this._hLines?.map(l => ( + <line x1="0" y1={l} x2="1000" y2={l} stroke="black" /> + ))} + {this._vLines?.map(l => ( + <line y1="0" x1={l} y2="1000" x2={l} stroke="black" /> + ))} + </svg> + </div> - {this.props.Document._isGroup && SnappingManager.GetIsDragging() && this.ChildDrag ? ( - <div - className="collectionFreeForm-groupDropper" - ref={this.createGroupEventsTarget} - style={{ - width: this.ChildDrag ? '10000' : '100%', - height: this.ChildDrag ? '10000' : '100%', - left: this.ChildDrag ? '-5000' : 0, - top: this.ChildDrag ? '-5000' : 0, - position: 'absolute', - background: '#0009930', - pointerEvents: 'all', - }} - /> - ) : null} + {this.props.Document._isGroup && SnappingManager.GetIsDragging() && this.ChildDrag ? ( + <div + className="collectionFreeForm-groupDropper" + ref={this.createGroupEventsTarget} + style={{ + width: this.ChildDrag ? '10000' : '100%', + height: this.ChildDrag ? '10000' : '100%', + left: this.ChildDrag ? '-5000' : 0, + top: this.ChildDrag ? '-5000' : 0, + position: 'absolute', + background: '#0009930', + pointerEvents: 'all', + }} + /> + ) : null} + </> + )} </div> ); } diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index d443df0f3..11d466b0f 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -39,16 +39,7 @@ interface MarqueeViewProps { nudge?: (x: number, y: number, nudgeTime?: number) => boolean; ungroup?: () => void; setPreviewCursor?: (func: (x: number, y: number, drag: boolean, hide: boolean) => void) => void; - slowLoadDocuments: ( - files: File[] | string, - options: DocumentOptions, - generatedDocuments: Doc[], - text: string, - completed: ((doc: Doc[]) => void) | undefined, - clientX: number, - clientY: number, - addDocument: (doc: Doc | Doc[]) => boolean - ) => Promise<void>; + slowLoadDocuments: (files: File[] | string, options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: ((doc: Doc[]) => void) | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => Promise<void>; } export interface MarqueeViewBounds { @@ -371,10 +362,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque @undoBatch @action - delete = () => { + delete = (e?: React.PointerEvent<Element> | KeyboardEvent | undefined, hide?: boolean) => { const selected = this.marqueeSelect(false); SelectionManager.DeselectAll(); - selected.forEach(doc => this.props.removeDocument?.(doc)); + selected.forEach(doc => (hide ? (doc.hidden = true) : this.props.removeDocument?.(doc))); this.cleanupInteractions(false); MarqueeOptionsMenu.Instance.fadeOut(true); @@ -425,9 +416,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque */ @undoBatch @action - pinWithView = async () => { - const doc = this.props.Document; - TabDocView.PinDoc(doc, { pinViewport: this.Bounds }); + pinWithView = () => { + TabDocView.PinDoc(this.props.Document, { pinViewport: this.Bounds }); MarqueeOptionsMenu.Instance.fadeOut(true); this.hideMarquee(); }; @@ -550,11 +540,11 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque if (this._commandExecuted || (e as any).propagationIsStopped) { return; } - if (e.key === 'Backspace' || e.key === 'Delete' || e.key === 'd') { + if (e.key === 'Backspace' || e.key === 'Delete' || e.key === 'd' || e.key === 'h') { this._commandExecuted = true; e.stopPropagation(); (e as any).propagationIsStopped = true; - this.delete(); + this.delete(e, e.key === 'h'); e.stopPropagation(); } if ('cbtsSpg'.indexOf(e.key) !== -1) { diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index 97eed7752..efd73a927 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -255,15 +255,17 @@ export class CollectionLinearView extends CollectionSubView() { })} /> - <div - className="collectionLinearView-content" - style={{ - height: this.dimension(), - flexDirection: flexDir as any, - gap: flexGap, - }}> - {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))} - </div> + {!this.layoutDoc.linearViewIsExpanded ? null : ( + <div + className="collectionLinearView-content" + style={{ + height: this.dimension(), + flexDirection: flexDir as any, + gap: flexGap, + }}> + {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))} + </div> + )} </div> </div> ); diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss index 1ef2fb4ef..a9434fde3 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss @@ -9,13 +9,18 @@ .schema-table { background-color: $white; cursor: grab; - overflow: scroll; + + .schema-table-content { + overflow: overlay; + scroll-behavior: smooth; + } .schema-column-menu, .schema-filter-menu { background: $light-gray; position: absolute; min-width: 200px; + max-width: 400px; display: flex; flex-direction: column; align-items: flex-start; @@ -26,14 +31,20 @@ margin: 10px; } - .schema-key-search-result { + .schema-search-result { cursor: pointer; - padding: 2px 10px; + padding: 5px 10px; width: 100%; &:hover { background-color: $medium-gray; } + + .schema-search-result-type, + .schema-search-result-desc { + color: $dark-gray; + font-size: $body-text; + } } .schema-key-search, diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index 6d5a73e55..a59d7e5a3 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -1,21 +1,22 @@ import React = require('react'); import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, observable, ObservableMap, untracked } from 'mobx'; +import { action, computed, observable, ObservableMap, observe, trace } from 'mobx'; import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; -import { Doc, Field, StrListCast } from '../../../../fields/Doc'; +import { Doc, DocListCast, Field, NumListCast, StrListCast } from '../../../../fields/Doc'; import { Id } from '../../../../fields/FieldSymbols'; import { List } from '../../../../fields/List'; import { listSpec } from '../../../../fields/Schema'; import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types'; -import { emptyFunction, returnDefault, returnEmptyDoclist, returnEmptyString, returnFalse, returnNever, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../Utils'; -import { Docs, DocUtils } from '../../../documents/Documents'; +import { emptyFunction, returnDefault, returnEmptyDoclist, returnEmptyString, returnFalse, returnIgnore, returnNever, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../Utils'; +import { Docs, DocumentOptions, DocUtils, FInfo } from '../../../documents/Documents'; import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager } from '../../../util/DragManager'; import { SelectionManager } from '../../../util/SelectionManager'; import { undoBatch } from '../../../util/UndoManager'; import { ContextMenu } from '../../ContextMenu'; import { EditableView } from '../../EditableView'; +import { Colors } from '../../global/globalEnums'; import { DocFocusOptions, DocumentView } from '../../nodes/DocumentView'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; import { KeyValueBox } from '../../nodes/KeyValueBox'; @@ -29,52 +30,59 @@ export enum ColumnType { Number, String, Boolean, - Doc, + Date, Image, + RTF, + Any, } +export const FInfotoColType: { [key: string]: ColumnType } = { + string: ColumnType.String, + number: ColumnType.Number, + boolean: ColumnType.Boolean, + date: ColumnType.Date, + image: ColumnType.Image, + rtf: ColumnType.RTF, +}; + const defaultColumnKeys: string[] = ['title', 'type', 'author', 'creationDate', 'text']; @observer export class CollectionSchemaView extends CollectionSubView() { + private _keysDisposer: any; private _closestDropIndex: number = 0; private _previewRef: HTMLDivElement | null = null; private _makeNewColumn: boolean = false; + private _documentOptions: DocumentOptions = new DocumentOptions(); + private _tableContentRef: HTMLDivElement | null = null; - public static _rowHeight: number = 40; + public static _rowHeight: number = 50; public static _minColWidth: number = 25; public static _rowMenuWidth: number = 60; public static _previewDividerWidth: number = 4; + public static _newNodeInputHeight: number = 30; + public fieldInfos = new ObservableMap<string, FInfo>(); - @computed get _selectedDocs() { - return SelectionManager.Docs().filter(doc => Doc.AreProtosEqual(DocCast(doc.context), this.props.Document)); - } + @observable _menuKeys: string[] = []; @observable _rowEles: ObservableMap = new ObservableMap<Doc, HTMLDivElement>(); @observable _colEles: HTMLDivElement[] = []; @observable _displayColumnWidths: number[] | undefined; @observable _columnMenuIndex: number | undefined; - @observable _menuOptions: string[] = []; @observable _newFieldWarning: string = ''; @observable _makeNewField: boolean = false; @observable _newFieldDefault: any = 0; @observable _newFieldType: ColumnType = ColumnType.Number; @observable _menuValue: string = ''; @observable _filterColumnIndex: number | undefined; - @observable _filterValue: string = ''; - - get documentKeys() { - const docs = this.childDocs; - const keys: { [key: string]: boolean } = {}; - // bcz: ugh. this is untracked since otherwise a large collection of documents will blast the server for all their fields. - // then as each document's fields come back, we update the documents _proxies. Each time we do this, the whole schema will be - // invalidated and re-rendered. This workaround will inquire all of the document fields before the options button is clicked. - // then by the time the options button is clicked, all of the fields should be in place. If a new field is added while this menu - // is displayed (unlikely) it won't show up until something else changes. - //TODO Types - untracked(() => docs.map(doc => Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => (keys[key] = false))))); - - // this.columns.forEach(key => (keys[key.heading] = true)); - return Array.from(Object.keys(keys)); + @observable _filterSearchValue: string = ''; + @observable _selectedCell: [Doc, number] | undefined; + + @computed get _selectedDocs() { + return SelectionManager.Docs().filter(doc => Doc.AreProtosEqual(DocCast(doc.context), this.rootDoc)); + } + + @computed get documentKeys() { + return Array.from(this.fieldInfos.keys()); } @computed get previewWidth() { @@ -90,20 +98,15 @@ export class CollectionSchemaView extends CollectionSubView() { } @computed get storedColumnWidths() { - let widths = Cast( + const widths = NumListCast( this.layoutDoc.columnWidths, - listSpec('number'), this.columnKeys.map(() => (this.tableWidth - CollectionSchemaView._rowMenuWidth) / this.columnKeys.length) ); const totalWidth = widths.reduce((sum, width) => sum + width, 0); if (totalWidth !== this.tableWidth - CollectionSchemaView._rowMenuWidth) { - widths = widths.map(w => { - const proportion = w / totalWidth; - return proportion * (this.tableWidth - CollectionSchemaView._rowMenuWidth); - }); + return widths.map(w => (w / totalWidth) * (this.tableWidth - CollectionSchemaView._rowMenuWidth)); } - return widths; } @@ -119,19 +122,37 @@ export class CollectionSchemaView extends CollectionSubView() { return BoolCast(this.layoutDoc.sortDesc); } - rowIndex(doc: Doc) { - return this.childDocs.indexOf(doc); - } - + @action componentDidMount() { this.props.setContentView?.(this); document.addEventListener('keydown', this.onKeyDown); + + Object.entries(this._documentOptions).forEach((pair: [string, FInfo]) => this.fieldInfos.set(pair[0], pair[1])); + this._keysDisposer = observe( + this.rootDoc[this.fieldKey ?? 'data'] as List<Doc>, + change => { + switch (change.type as any) { + case 'splice': + // prettier-ignore + (change as any).added.forEach((doc: Doc) => // for each document added + Doc.GetAllPrototypes(doc.value as Doc).forEach(proto => // for all of its prototypes (and itself) + Object.keys(proto).forEach(action(key => // check if any of its keys are new, and add them + !this.fieldInfos.get(key) && this.fieldInfos.set(key, new FInfo('')))))); + break; + case 'update': //let oldValue = change.oldValue; // fill this in if the entire child list will ever be reassigned with a new list + } + }, + true + ); } componentWillUnmount() { + this._keysDisposer?.(); document.removeEventListener('keydown', this.onKeyDown); } + rowIndex = (doc: Doc) => this.sortedDocs.docs.indexOf(doc); + @action onKeyDown = (e: KeyboardEvent) => { if (this._selectedDocs.length > 0) { @@ -140,30 +161,62 @@ export class CollectionSchemaView extends CollectionSubView() { { const lastDoc = this._selectedDocs.lastElement(); const lastIndex = this.rowIndex(lastDoc); - const curDoc = this.childDocs[lastIndex]; + const curDoc = this.sortedDocs.docs[lastIndex]; if (lastIndex >= 0 && lastIndex < this.childDocs.length - 1) { !e.shiftKey && this.clearSelection(); - const newDoc = this.childDocs[lastIndex + 1]; - if (this._selectedDocs.includes(newDoc)) SelectionManager.DeselectView(DocumentManager.Instance.getFirstDocumentView(curDoc)); - else this.addDocToSelection(newDoc, e.shiftKey, lastIndex + 1); + const newDoc = this.sortedDocs.docs[lastIndex + 1]; + if (this._selectedDocs.includes(newDoc)) { + SelectionManager.DeselectView(DocumentManager.Instance.getFirstDocumentView(curDoc)); + } else { + this.addDocToSelection(newDoc, e.shiftKey, lastIndex + 1); + this._selectedCell && (this._selectedCell[0] = newDoc); + this.scrollToDoc(newDoc, {}); + } } e.stopPropagation(); + e.preventDefault(); } break; case 'ArrowUp': { const firstDoc = this._selectedDocs.lastElement(); const firstIndex = this.rowIndex(firstDoc); - const curDoc = this.childDocs[firstIndex]; + const curDoc = this.sortedDocs.docs[firstIndex]; if (firstIndex > 0 && firstIndex < this.childDocs.length) { !e.shiftKey && this.clearSelection(); - const newDoc = this.childDocs[firstIndex - 1]; + const newDoc = this.sortedDocs.docs[firstIndex - 1]; if (this._selectedDocs.includes(newDoc)) SelectionManager.DeselectView(DocumentManager.Instance.getFirstDocumentView(curDoc)); - else this.addDocToSelection(newDoc, e.shiftKey, firstIndex - 1); + else { + this.addDocToSelection(newDoc, e.shiftKey, firstIndex - 1); + this._selectedCell && (this._selectedCell[0] = newDoc); + this.scrollToDoc(newDoc, {}); + } } e.stopPropagation(); + e.preventDefault(); + } + break; + case 'ArrowRight': + if (this._selectedCell) { + this._selectedCell[1] = Math.min(this._selectedCell[1] + 1, this.columnKeys.length - 1); + } else if (this._selectedDocs.length > 0) { + this.selectCell(this._selectedDocs[0], 0); } break; + case 'ArrowLeft': + if (this._selectedCell) { + this._selectedCell[1] = Math.max(this._selectedCell[1] - 1, 0); + } else if (this._selectedDocs.length > 0) { + this.selectCell(this._selectedDocs[0], 0); + } + break; + case 'Backspace': { + this.removeDocument(this._selectedDocs); + break; + } + case 'Escape': { + this.deselectCell(); + } } } }; @@ -177,7 +230,6 @@ export class CollectionSchemaView extends CollectionSubView() { addRow = (doc: Doc | Doc[]) => { const result: boolean = this.addDocument(doc); - this.setSort(this.sortField, this.sortDesc); return result; }; @@ -270,18 +322,13 @@ export class CollectionSchemaView extends CollectionSubView() { @undoBatch @action - swapColumns = (index1: number, index2: number) => { - const tempKey = this.columnKeys[index1]; - const tempWidth = this.storedColumnWidths[index1]; - - let currKeys = this.columnKeys; - currKeys[index1] = currKeys[index2]; - currKeys[index2] = tempKey; + moveColumn = (fromIndex: number, toIndex: number) => { + let currKeys = this.columnKeys.slice(); + currKeys.splice(toIndex, 0, currKeys.splice(fromIndex, 1)[0]); this.layoutDoc.columnKeys = new List<string>(currKeys); - let currWidths = this.storedColumnWidths; - currWidths[index1] = currWidths[index2]; - currWidths[index2] = tempWidth; + let currWidths = this.storedColumnWidths.slice(); + currWidths.splice(toIndex, 0, currWidths.splice(fromIndex, 1)[0]); this.layoutDoc.columnWidths = new List<number>(currWidths); }; @@ -294,10 +341,45 @@ export class CollectionSchemaView extends CollectionSubView() { }); DragManager.StartColumnDrag(dragEles, dragData, e.x, e.y); + document.removeEventListener('pointermove', this.highlightDropColumn); + document.addEventListener('pointermove', this.highlightDropColumn); + let stopHighlight = (e: PointerEvent) => { + document.removeEventListener('pointermove', this.highlightDropColumn); + document.removeEventListener('pointerup', stopHighlight); + }; + document.addEventListener('pointerup', stopHighlight); + return true; }; @action + highlightDropColumn = (e: PointerEvent) => { + e.stopPropagation(); + const mouseX = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY)[0]; + let index: number | undefined; + this.displayColumnWidths.reduce((total, curr, i) => { + if (total <= mouseX && total + curr >= mouseX) { + if (mouseX <= total + curr / 2) index = i; + else index = i + 1; + } + return total + curr; + }, CollectionSchemaView._rowMenuWidth); + + this._colEles.forEach((colRef, i) => { + let leftStyle = ''; + let rightStyle = ''; + if (i + 1 === index) rightStyle = `solid 2px ${Colors.MEDIUM_BLUE}`; + if (i === index && i === 0) leftStyle = `solid 2px ${Colors.MEDIUM_BLUE}`; + colRef.style.borderLeft = leftStyle; + colRef.style.borderRight = rightStyle; + this.childDocs.forEach(doc => { + this._rowEles.get(doc).children[1].children[i].style.borderLeft = leftStyle; + this._rowEles.get(doc).children[1].children[i].style.borderRight = rightStyle; + }); + }); + }; + + @action addRowRef = (doc: Doc, ref: HTMLDivElement) => this._rowEles.set(doc, ref); @action @@ -319,17 +401,23 @@ export class CollectionSchemaView extends CollectionSubView() { clearSelection = () => SelectionManager.DeselectAll(); selectRows = (rootDoc: Doc, lastSelected: Doc) => { - const index = this.childDocs.indexOf(rootDoc); - const lastSelectedRow = this.childDocs.indexOf(lastSelected); + const index = this.rowIndex(rootDoc); + const lastSelectedRow = this.rowIndex(lastSelected); const startRow = Math.min(lastSelectedRow, index); const endRow = Math.max(lastSelectedRow, index); for (let i = startRow; i <= endRow; i++) { - const currDoc = this.childDocs[i]; + const currDoc = this.sortedDocs.docs[i]; if (!this._selectedDocs.includes(currDoc)) this.addDocToSelection(currDoc, true, i); } }; - sortedSelectedDocs = () => this.childDocs.filter(doc => this._selectedDocs.includes(doc)); + @action + selectCell = (doc: Doc, index: number) => (this._selectedCell = [doc, index]); + + @action + deselectCell = () => (this._selectedCell = undefined); + + sortedSelectedDocs = () => this.sortedDocs.docs.filter(doc => this._selectedDocs.includes(doc)); setDropIndex = (index: number) => (this._closestDropIndex = index); @@ -338,25 +426,34 @@ export class CollectionSchemaView extends CollectionSubView() { if (de.complete.columnDragData) { e.stopPropagation(); const mouseX = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y)[0]; - let i = de.complete.columnDragData.colIndex; - this.displayColumnWidths.reduce((total, curr, index) => { + let index = de.complete.columnDragData.colIndex; + this.displayColumnWidths.reduce((total, curr, i) => { if (total <= mouseX && total + curr >= mouseX) { - i = index; + if (mouseX <= total + curr / 2) index = i; + else index = i + 1; } return total + curr; }, CollectionSchemaView._rowMenuWidth); - this.swapColumns(de.complete.columnDragData.colIndex, i); - e.stopPropagation(); + this.moveColumn(de.complete.columnDragData.colIndex, index); + + this._colEles.forEach((colRef, i) => { + colRef.style.borderLeft = ''; + colRef.style.borderRight = ''; + this.childDocs.forEach(doc => { + this._rowEles.get(doc).children[1].children[i].style.borderLeft = ''; + this._rowEles.get(doc).children[1].children[i].style.borderRight = ''; + }); + }); + return true; } const draggedDocs = de.complete.docDragData?.draggedDocuments; - if (draggedDocs && super.onInternalDrop(e, de)) { + if (draggedDocs && super.onInternalDrop(e, de) && !this.sortField) { const pushedDocs = this.childDocs.filter((doc, index) => index >= this._closestDropIndex && !draggedDocs.includes(doc)); const pushedAndDraggedDocs = [...pushedDocs, ...draggedDocs]; const removed = this.childDocs.slice().filter(doc => !pushedAndDraggedDocs.includes(doc)); this.dataDoc[this.fieldKey ?? 'data'] = new List<Doc>([...removed, ...draggedDocs, ...pushedDocs]); - this.setSort(undefined); - SelectionManager.DeselectAll(); + this.clearSelection(); draggedDocs.forEach(doc => { const draggedView = DocumentManager.Instance.getFirstDocumentView(doc); if (draggedView) DocumentManager.Instance.RemoveView(draggedView); @@ -371,7 +468,6 @@ export class CollectionSchemaView extends CollectionSubView() { @action onExternalDrop = async (e: React.DragEvent): Promise<void> => { super.onExternalDrop(e, {}, undoBatch(action(docus => docus.map((doc: Doc) => this.addDocument(doc))))); - this.setSort(undefined); }; onDividerDown = (e: React.PointerEvent) => setupMoveUpEvents(this, e, this.onDividerMove, emptyFunction, emptyFunction); @@ -410,18 +506,21 @@ export class CollectionSchemaView extends CollectionSubView() { focusDocument = (doc: Doc, options: DocFocusOptions) => { Doc.BrushDoc(doc); + this.scrollToDoc(doc, options); + return undefined; + }; - const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]); + scrollToDoc = (doc: Doc, options: DocFocusOptions) => { + const found = this._tableContentRef && Array.from(this._tableContentRef.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) { - let focusSpeed = options.zoomTime ?? 500; - smoothScroll(focusSpeed, this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc); + const rect = found.getBoundingClientRect(); + const localRect = this.props.ScreenToLocalTransform().transformBounds(rect.left, rect.top, rect.width, rect.height); + if (localRect.y < CollectionSchemaView._rowHeight || localRect.y + localRect.height > this.props.PanelHeight()) { + let focusSpeed = options.zoomTime ?? 50; + smoothScroll(focusSpeed, this._tableContentRef!, localRect.y + this._tableContentRef!.scrollTop - CollectionSchemaView._rowHeight, options.easeFunc); return focusSpeed; } } - return undefined; }; @computed get fieldDefaultInput() { @@ -443,7 +542,7 @@ export class CollectionSchemaView extends CollectionSubView() { onSearchKeyDown = (e: React.KeyboardEvent) => { switch (e.key) { case 'Enter': - this._menuOptions.length > 0 && this._menuValue.length > 0 ? this.setKey(this._menuOptions[0]) : action(() => (this._makeNewField = true))(); + this._menuKeys.length > 0 && this._menuValue.length > 0 ? this.setKey(this._menuKeys[0]) : action(() => (this._makeNewField = true))(); break; case 'Escape': this.closeColumnMenu(); @@ -472,7 +571,7 @@ export class CollectionSchemaView extends CollectionSubView() { this._makeNewColumn = false; this._columnMenuIndex = index; this._menuValue = ''; - this._menuOptions = this.documentKeys; + this._menuKeys = this.documentKeys; this._makeNewField = false; this._newFieldWarning = ''; this._makeNewField = false; @@ -485,24 +584,17 @@ export class CollectionSchemaView extends CollectionSubView() { @action openFilterMenu = (index: number) => { this._filterColumnIndex = index; - this._filterValue = this.getFieldFilters(this.columnKeys[this._filterColumnIndex!]).map(filter => filter.split(':')[1])[0]; + this._filterSearchValue = ''; }; @action - closeFilterMenu = (setValue: boolean) => { - if (setValue) { - if (this._filterValue !== '') { - Doc.setDocFilter(this.Document, this.columnKeys[this._filterColumnIndex!], this._filterValue, 'check', false, undefined, false); - } else { - this.removeFieldFilters(this.columnKeys[this._filterColumnIndex!]); - } - } + closeFilterMenu = () => { this._filterColumnIndex = undefined; }; openContextMenu = (x: number, y: number, index: number) => { this.closeColumnMenu(); - this.closeFilterMenu(false); + this.closeFilterMenu(); ContextMenu.Instance.clearItems(); ContextMenu.Instance.addItem({ description: 'Change field', @@ -525,7 +617,7 @@ export class CollectionSchemaView extends CollectionSubView() { @action updateKeySearch = (e: React.ChangeEvent<HTMLInputElement>) => { this._menuValue = e.target.value; - this._menuOptions = this.documentKeys.filter(value => value.toLowerCase().includes(this._menuValue.toLowerCase())); + this._menuKeys = this.documentKeys.filter(value => value.toLowerCase().includes(this._menuValue.toLowerCase())); }; getFieldFilters = (field: string) => StrListCast(this.Document._docFilters).filter(filter => filter.split(':')[0] == field); @@ -535,15 +627,16 @@ export class CollectionSchemaView extends CollectionSubView() { }; onFilterKeyDown = (e: React.KeyboardEvent) => { - //prettier-ignore switch (e.key) { - case 'Enter' : this.closeFilterMenu(true); break; - case 'Escape': this.closeFilterMenu(false);break; + case 'Enter': + case 'Escape': + this.closeFilterMenu(); + break; } }; @action - updateFilterSearch = (e: React.ChangeEvent<HTMLInputElement>) => (this._filterValue = e.target.value); + updateFilterSearch = (e: React.ChangeEvent<HTMLInputElement>) => (this._filterSearchValue = e.target.value); @computed get newFieldMenu() { return ( @@ -626,14 +719,23 @@ export class CollectionSchemaView extends CollectionSubView() { { passive: false } ) }> - {this._menuOptions.map(key => ( + {this._menuKeys.map(key => ( <div - className="schema-key-search-result" + className="schema-search-result" onPointerDown={e => { e.stopPropagation(); this.setKey(key); }}> - {key} + <p> + <span className="schema-search-result-key"> + {key} + {this.fieldInfos.get(key)!.fieldType ? ', ' : ''} + </span> + <span className="schema-search-result-type" style={{ color: this.fieldInfos.get(key)!.readOnly ? 'red' : 'inherit' }}> + {this.fieldInfos.get(key)!.fieldType} + </span> + </p> + <p className="schema-search-result-desc">{this.fieldInfos.get(key)!.description}</p> </div> ))} </div> @@ -662,21 +764,16 @@ export class CollectionSchemaView extends CollectionSubView() { @computed get renderFilterOptions() { const keyOptions: string[] = []; const columnKey = this.columnKeys[this._filterColumnIndex!]; - this.childDocs.forEach(doc => { - const key = StrCast(doc[columnKey]); - if (keyOptions.includes(key) === false && (key.includes(this._filterValue) || !this._filterValue) && key !== '') { - keyOptions.push(key); + const allDocs = DocListCast(this.dataDoc[this.props.fieldKey]); + allDocs.forEach(doc => { + const value = StrCast(doc[columnKey]); + if (!keyOptions.includes(value) && value !== '' && (this._filterSearchValue === '' || value.includes(this._filterSearchValue))) { + keyOptions.push(value); } }); const filters = StrListCast(this.Document._docFilters); - for (let i = 0; i < (filters?.length ?? 0) - 1; i++) { - if (filters[i] === columnKey && keyOptions.includes(filters[i].split(':')[1]) === false) { - keyOptions.push(filters[i + 1]); - } - } - - const options = keyOptions.map(key => { + return keyOptions.map(key => { let bool = false; if (filters !== undefined) { const ind = filters.findIndex(filter => filter.split(':')[1] === key); @@ -702,21 +799,19 @@ export class CollectionSchemaView extends CollectionSubView() { </div> ); }); - - return options; } @computed get renderFilterMenu() { const x = this.displayColumnWidths.reduce((total, curr, index) => total + (index < this._filterColumnIndex! ? curr : 0), CollectionSchemaView._rowMenuWidth); return ( <div className="schema-filter-menu" style={{ left: x, minWidth: CollectionSchemaView._minColWidth }}> - <input className="schema-filter-input" type="text" value={this._filterValue} onKeyDown={this.onFilterKeyDown} onChange={this.updateFilterSearch} onPointerDown={e => e.stopPropagation()} /> + <input className="schema-filter-input" type="text" value={this._filterSearchValue} onKeyDown={this.onFilterKeyDown} onChange={this.updateFilterSearch} onPointerDown={e => e.stopPropagation()} /> {this.renderFilterOptions} <div className="schema-column-menu-button" onPointerDown={action(e => { e.stopPropagation(); - this.closeFilterMenu(true); + this.closeFilterMenu(); })}> done </div> @@ -725,11 +820,12 @@ export class CollectionSchemaView extends CollectionSubView() { } @computed get sortedDocs() { + trace(); const field = StrCast(this.layoutDoc.sortField); const desc = BoolCast(this.layoutDoc.sortDesc); const docs = !field ? this.childDocs - : this.childDocs.sort((docA, docB) => { + : [...this.childDocs].sort((docA, docB) => { const aStr = Field.toString(docA[field] as Field); const bStr = Field.toString(docB[field] as Field); var out = 0; @@ -779,9 +875,8 @@ export class CollectionSchemaView extends CollectionSubView() { </div> {this._columnMenuIndex !== undefined && this.renderColumnMenu} {this._filterColumnIndex !== undefined && this.renderFilterMenu} - <CollectionSchemaViewDocs schema={this} childDocs={this.sortedDocsFunc} /> - - <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} placeholder={"Type ':' for commands"} contents={'+ New Node'} menuCallback={this.menuCallback} /> + <CollectionSchemaViewDocs schema={this} childDocs={this.sortedDocsFunc} setRef={(ref: HTMLDivElement | null) => (this._tableContentRef = ref)} /> + <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} placeholder={"Type ':' for commands"} contents={'+ New Node'} menuCallback={this.menuCallback} height={CollectionSchemaView._newNodeInputHeight} /> </div> {this.previewWidth > 0 && <div className="schema-preview-divider" style={{ width: CollectionSchemaView._previewDividerWidth }} onPointerDown={this.onDividerDown}></div>} {this.previewWidth > 0 && ( @@ -794,6 +889,7 @@ export class CollectionSchemaView extends CollectionSubView() { dontCenter={'y'} onClickScriptDisable="always" focus={emptyFunction} + defaultDoubleClick={returnIgnore} renderDepth={this.props.renderDepth + 1} rootSelected={this.rootSelected} PanelWidth={this.previewWidthFunc} @@ -809,7 +905,7 @@ export class CollectionSchemaView extends CollectionSubView() { moveDocument={this.props.moveDocument} addDocument={this.addRow} removeDocument={this.props.removeDocument} - whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} + whenChildContentsActiveChanged={returnFalse} addDocTab={this.props.addDocTab} pinToPres={this.props.pinToPres} bringToFront={returnFalse} @@ -824,6 +920,7 @@ export class CollectionSchemaView extends CollectionSubView() { interface CollectionSchemaViewDocsProps { schema: CollectionSchemaView; + setRef: (ref: HTMLDivElement | null) => void; childDocs: () => { docs: Doc[] }; } @@ -834,11 +931,11 @@ class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsP childScreenToLocal = computedFn((index: number) => () => this.props.schema.props.ScreenToLocalTransform().translate(0, -CollectionSchemaView._rowHeight - index * this.rowHeightFunc())); render() { return ( - <div className="schema-table-content"> + <div className="schema-table-content" ref={this.props.setRef} style={{ height: `calc(100% - ${CollectionSchemaView._newNodeInputHeight + CollectionSchemaView._rowHeight}px)` }}> {this.props.childDocs().docs.map((doc: Doc, index: number) => { const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField ? undefined : this.props.schema.props.DataDoc; return ( - <div className="schema-row-wrapper" style={{ maxHeight: CollectionSchemaView._rowHeight }}> + <div className="schema-row-wrapper" style={{ height: CollectionSchemaView._rowHeight }}> <DocumentView key={doc[Id]} {...this.props.schema.props} @@ -851,7 +948,7 @@ class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsP PanelHeight={this.rowHeightFunc} styleProvider={DefaultStyleProvider} waitForDoubleClickToClick={returnNever} - defaultDoubleClick={returnDefault} + defaultDoubleClick={returnIgnore} enableDragWhenActive={true} onClickScriptDisable="always" focus={this.props.schema.focusDocument} @@ -863,13 +960,14 @@ class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsP bringToFront={emptyFunction} isDocumentActive={this.props.schema.props.childDocumentsActive?.() ? this.props.schema.props.isDocumentActive : this.props.schema.isContentActive} isContentActive={emptyFunction} - whenChildContentsActiveChanged={active => this.props.schema.props.whenChildContentsActiveChanged(active)} + whenChildContentsActiveChanged={this.props.schema.props.whenChildContentsActiveChanged} hideDecorations={true} hideTitle={true} hideDocumentButtonBar={true} hideLinkAnchors={true} fitWidth={returnTrue} scriptContext={this} + canEmbedOnDrag={true} /> </div> ); diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx index d88d67c94..243fe0c61 100644 --- a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx +++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx @@ -1,10 +1,12 @@ import React = require('react'); import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed } from 'mobx'; +import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import { emptyFunction, setupMoveUpEvents } from '../../../../Utils'; import { Colors } from '../../global/globalEnums'; import './CollectionSchemaView.scss'; +import { SnappingManager } from '../../../util/SnappingManager'; +import { DragManager } from '../../../util/DragManager'; export interface SchemaColumnHeaderProps { columnKeys: string[]; @@ -12,7 +14,7 @@ export interface SchemaColumnHeaderProps { columnIndex: number; sortField: string; sortDesc: boolean; - setSort: (field: string, desc: boolean) => void; + setSort: (field: string | undefined, desc?: boolean) => void; removeColumn: (index: number) => void; resizeColumn: (e: any, index: number) => void; dragColumn: (e: any, index: number) => boolean; @@ -22,6 +24,8 @@ export interface SchemaColumnHeaderProps { @observer export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps> { + @observable _ref: HTMLDivElement | null = null; + @computed get fieldKey() { return this.props.columnKeys[this.props.columnIndex]; } @@ -30,8 +34,10 @@ export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps> sortClicked = (e: React.PointerEvent) => { e.stopPropagation(); e.preventDefault(); - if (this.props.sortField == this.fieldKey) { - this.props.setSort(this.fieldKey, !this.props.sortDesc); + if (this.props.sortField == this.fieldKey && this.props.sortDesc) { + this.props.setSort(undefined); + } else if (this.props.sortField == this.fieldKey) { + this.props.setSort(this.fieldKey, true); } else { this.props.setSort(this.fieldKey, false); } @@ -44,7 +50,18 @@ export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps> render() { return ( - <div className="schema-column-header" style={{ width: this.props.columnWidths[this.props.columnIndex] }} onPointerDown={this.onPointerDown} ref={col => col && this.props.setColRef(this.props.columnIndex, col)}> + <div + className="schema-column-header" + style={{ + width: this.props.columnWidths[this.props.columnIndex], + }} + onPointerDown={this.onPointerDown} + ref={col => { + if (col) { + this._ref = col; + this.props.setColRef(this.props.columnIndex, col); + } + }}> <div className="schema-column-resizer left" onPointerDown={e => this.props.resizeColumn(e, this.props.columnIndex)}></div> <div className="schema-column-title">{this.fieldKey}</div> diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx index 856537927..ca9e0bda0 100644 --- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx +++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx @@ -12,6 +12,8 @@ import { FieldView, FieldViewProps } from '../../nodes/FieldView'; import { CollectionSchemaView } from './CollectionSchemaView'; import './CollectionSchemaView.scss'; import { SchemaTableCell } from './SchemaTableCell'; +import { computedFn } from 'mobx-utils'; +import { Doc } from '../../../../fields/Doc'; @observer export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() { @@ -24,8 +26,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() { bounds = () => this._ref?.getBoundingClientRect(); @computed get schemaView() { - const vpath = this.props.docViewPath(); - return vpath.length > 1 ? (vpath[vpath.length - 2].ComponentView as CollectionSchemaView) : undefined; + return this.props.DocumentView?.().props.docViewPath().lastElement()?.ComponentView as CollectionSchemaView; } @computed get schemaDoc() { @@ -84,11 +85,17 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() { document.removeEventListener('pointermove', this.onPointerMove); }; + getFinfo = computedFn((fieldKey: string) => this.schemaView?.fieldInfos.get(fieldKey)); + selectCell = (doc: Doc, col: number) => this.schemaView?.selectCell(doc, col); + deselectCell = () => this.schemaView?.deselectCell(); + selectedCell = () => this.schemaView?._selectedCell; + setColumnValues = (field: any, value: any) => this.schemaView?.setColumnValues(field, value) ?? false; + columnWidth = computedFn((index: number) => () => this.schemaView?.displayColumnWidths[index] ?? CollectionSchemaView._minColWidth); render() { return ( <div className="schema-row" - style={{ height: CollectionSchemaView._rowHeight, backgroundColor: this.props.isSelected() ? Colors.LIGHT_BLUE : undefined, pointerEvents: this.schemaView?.props.isContentActive() ? 'all' : undefined }} + style={{ height: CollectionSchemaView._rowHeight, backgroundColor: this.props.isSelected() ? Colors.LIGHT_BLUE : undefined }} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave} ref={(row: HTMLDivElement | null) => { @@ -123,10 +130,15 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() { <SchemaTableCell key={key} Document={this.rootDoc} + col={index} fieldKey={key} - columnWidth={this.schemaView?.displayColumnWidths[index] ?? CollectionSchemaView._minColWidth} + columnWidth={this.columnWidth(index)} isRowActive={this.props.isContentActive} - setColumnValues={(field, value) => this.schemaView?.setColumnValues(field, value) ?? false} + getFinfo={this.getFinfo} + selectCell={this.selectCell} + deselectCell={this.deselectCell} + selectedCell={this.selectedCell} + setColumnValues={this.setColumnValues} /> ))} </div> diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx index 5f8ffe8b0..712bd4491 100644 --- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx @@ -1,66 +1,296 @@ import React = require('react'); +import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; -import { Doc, Field } from '../../../../fields/Doc'; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../../Utils'; +import { extname } from 'path'; +import DatePicker from 'react-datepicker'; +import { DateField } from '../../../../fields/DateField'; +import { Doc, DocListCast, Field } from '../../../../fields/Doc'; +import { BoolCast, Cast, DateCast, FieldValue } from '../../../../fields/Types'; +import { ImageField } from '../../../../fields/URLField'; +import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero, Utils } from '../../../../Utils'; +import { FInfo } from '../../../documents/Documents'; +import { dropActionType } from '../../../util/DragManager'; import { Transform } from '../../../util/Transform'; +import { undoBatch } from '../../../util/UndoManager'; import { EditableView } from '../../EditableView'; +import { Colors } from '../../global/globalEnums'; import { FieldView, FieldViewProps } from '../../nodes/FieldView'; import { KeyValueBox } from '../../nodes/KeyValueBox'; import { DefaultStyleProvider } from '../../StyleProvider'; -import { CollectionSchemaView } from './CollectionSchemaView'; +import { CollectionSchemaView, ColumnType, FInfotoColType } from './CollectionSchemaView'; import './CollectionSchemaView.scss'; +import { RichTextField } from '../../../../fields/RichTextField'; +import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; export interface SchemaTableCellProps { Document: Doc; + col: number; + deselectCell: () => void; + selectCell: (doc: Doc, col: number) => void; + selectedCell: () => [Doc, number] | undefined; fieldKey: string; - columnWidth: number; + columnWidth: () => number; isRowActive: () => boolean | undefined; + getFinfo: (fieldKey: string) => FInfo | undefined; setColumnValues: (field: string, value: string) => boolean; } @observer export class SchemaTableCell extends React.Component<SchemaTableCellProps> { - render() { - const props: FieldViewProps = { - Document: this.props.Document, + public static colRowHeightFunc() { + return CollectionSchemaView._rowHeight; + } + public static renderProps(props: SchemaTableCellProps) { + const { Document, fieldKey, getFinfo, columnWidth, isRowActive } = props; + let protoCount = 0; + let doc: Doc | undefined = Document; + while (doc) { + if (Object.keys(doc).includes(fieldKey.replace(/^_/, ''))) { + break; + } + protoCount++; + doc = doc.proto; + } + const parenCount = Math.max(0, protoCount - 1); + const color = protoCount === 0 || (fieldKey.startsWith('_') && Document[fieldKey] === undefined) ? 'black' : 'blue'; + const textDecoration = color !== 'black' && parenCount ? 'underline' : ''; + const fieldProps: FieldViewProps = { docFilters: returnEmptyFilter, docRangeFilters: returnEmptyFilter, searchFilterDocs: returnEmptyDoclist, styleProvider: DefaultStyleProvider, docViewPath: returnEmptyDoclist, - fieldKey: this.props.fieldKey, rootSelected: returnFalse, isSelected: returnFalse, setHeight: returnFalse, select: emptyFunction, - dropAction: 'alias', + dropAction: 'alias' as dropActionType, bringToFront: emptyFunction, renderDepth: 1, isContentActive: returnFalse, whenChildContentsActiveChanged: emptyFunction, ScreenToLocalTransform: Transform.Identity, focus: emptyFunction, - PanelWidth: () => this.props.columnWidth, - PanelHeight: () => CollectionSchemaView._rowHeight, addDocTab: returnFalse, pinToPres: returnZero, + Document, + fieldKey, + PanelWidth: columnWidth, + PanelHeight: SchemaTableCell.colRowHeightFunc, }; + const readOnly = getFinfo(fieldKey)?.readOnly ?? false; + const cursor = !readOnly ? 'text' : 'default'; + const pointerEvents: 'all' | 'none' = !readOnly && isRowActive() ? 'all' : 'none'; + return { color, textDecoration, fieldProps, cursor, pointerEvents }; + } + + @computed get selected() { + const selected: [Doc, number] | undefined = this.props.selectedCell(); + return this.props.isRowActive() && selected?.[0] === this.props.Document && selected[1] === this.props.col; + } + + @computed get defaultCellContent() { + const { color, textDecoration, fieldProps } = SchemaTableCell.renderProps(this.props); + + return ( + <div + className="schemacell-edit-wrapper" + style={{ + color, + textDecoration, + }}> + <EditableView + contents={<FieldView {...fieldProps} />} + editing={this.selected ? undefined : false} + GetValue={() => Field.toKeyValueString(this.props.Document, this.props.fieldKey)} + SetValue={undoBatch((value: string, shiftDown?: boolean, enterKey?: boolean) => { + if (shiftDown && enterKey) { + this.props.setColumnValues(this.props.fieldKey.replace(/^_/, ''), value); + } + return KeyValueBox.SetField(this.props.Document, this.props.fieldKey.replace(/^_/, ''), value); + })} + /> + </div> + ); + } + + get getCellType() { + const cellValue = this.props.Document[this.props.fieldKey]; + if (cellValue instanceof ImageField) return ColumnType.Image; + if (cellValue instanceof DateField) return ColumnType.Date; + if (cellValue instanceof RichTextField) return ColumnType.RTF; + if (typeof cellValue === 'number') return ColumnType.Any; + if (typeof cellValue === 'string') return ColumnType.Any; + if (typeof cellValue === 'boolean') return ColumnType.Any; + + const columnTypeStr = this.props.getFinfo(this.props.fieldKey)?.fieldType; + if (columnTypeStr && columnTypeStr in FInfotoColType) { + return FInfotoColType[columnTypeStr]; + } + + return ColumnType.Any; + } + + get content() { + const cellType: ColumnType = this.getCellType; + // prettier-ignore + switch (cellType) { + case ColumnType.Image: return <SchemaImageCell {...this.props} />; + case ColumnType.Boolean: return <SchemaBoolCell {...this.props} />; + case ColumnType.RTF: return <SchemaRTFCell {...this.props} />; + case ColumnType.Date: // return <SchemaDateCell {...this.props} />; + default: return this.defaultCellContent; + } + } + + render() { + return ( + <div + className="schema-table-cell" + onPointerDown={action(e => !this.selected && this.props.selectCell(this.props.Document, this.props.col))} + style={{ width: this.props.columnWidth(), border: this.selected ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined }}> + {this.content} + </div> + ); + } +} + +// mj: most of this is adapted from old schema code so I'm not sure what it does tbh +@observer +export class SchemaImageCell extends React.Component<SchemaTableCellProps> { + @observable _previewRef: HTMLImageElement | undefined; + + choosePath(url: URL) { + if (url.protocol === 'data') return url.href; // if the url ises the data protocol, just return the href + if (url.href.indexOf(window.location.origin) === -1) return Utils.CorsProxy(url.href); // otherwise, put it through the cors proxy erver + if (!/\.(png|jpg|jpeg|gif|webp)$/.test(url.href.toLowerCase())) return url.href; //Why is this here — good question + + const ext = extname(url.href); + return url.href.replace(ext, '_s' + ext); + } + get url() { + const field = Cast(this.props.Document[this.props.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc + const alts = DocListCast(this.props.Document[this.props.fieldKey + '-alternates']); // retrieve alternate documents that may be rendered as alternate images + const altpaths = alts + .map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url) + .filter(url => url) + .map(url => this.choosePath(url)); // access the primary layout data of the alternate documents + const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths; + // If there is a path, follow it; otherwise, follow a link to a default image icon + const url = paths.length ? paths : [Utils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png')]; + return url[0]; + } + + @action + showHoverPreview = (e: React.PointerEvent) => { + this._previewRef = document.createElement('img'); + document.body.appendChild(this._previewRef); + const ext = extname(this.url); + this._previewRef.src = this.url.replace('_s' + ext, '_m' + ext); + this._previewRef.style.position = 'absolute'; + this._previewRef.style.left = e.clientX + 10 + 'px'; + this._previewRef.style.top = e.clientY + 10 + 'px'; + this._previewRef.style.zIndex = '1000'; + }; + + @action + moveHoverPreview = (e: React.PointerEvent) => { + if (!this._previewRef) return; + this._previewRef.style.left = e.clientX + 10 + 'px'; + this._previewRef.style.top = e.clientY + 10 + 'px'; + }; + + @action + removeHoverPreview = (e: React.PointerEvent) => { + if (!this._previewRef) return; + document.body.removeChild(this._previewRef); + }; + + render() { + const aspect = Doc.NativeAspect(this.props.Document); // aspect ratio + // let width = Math.max(75, this.props.columnWidth); // get a with that is no smaller than 75px + // const height = Math.max(75, width / aspect); // get a height either proportional to that or 75 px + const height = CollectionSchemaView._rowHeight - 10; + const width = height * aspect; // increase the width of the image if necessary to maintain proportionality + + return <img src={this.url} width={width} height={height} style={{}} draggable="false" onPointerEnter={this.showHoverPreview} onPointerMove={this.moveHoverPreview} onPointerLeave={this.removeHoverPreview} />; + } +} + +@observer +export class SchemaDateCell extends React.Component<SchemaTableCellProps> { + @observable _pickingDate: boolean = false; + + @computed get date(): DateField { + // if the cell is a date field, cast then contents to a date. Otherrwwise, make the contents undefined. + return DateCast(this.props.Document[this.props.fieldKey]); + } + + @action + handleChange = (date: any) => { + // const script = CompileScript(date.toString(), { requiredType: "Date", addReturn: true, params: { this: Doc.name } }); + // if (script.compiled) { + // this.applyToDoc(this._document, this.props.row, this.props.col, script.run); + // } else { + // ^ DateCast is always undefined for some reason, but that is what the field should be set to + this.props.Document[this.props.fieldKey] = new DateField(date as Date); + //} + }; + + render() { + return <DatePicker dateFormat={'Pp'} selected={this.date.date} onChange={(date: any) => this.handleChange(date)} />; + } +} +@observer +export class SchemaRTFCell extends React.Component<SchemaTableCellProps> { + @computed get selected() { + const selected: [Doc, number] | undefined = this.props.selectedCell(); + return this.props.isRowActive() && selected?.[0] === this.props.Document && selected[1] === this.props.col; + } + selectedFunc = () => this.selected; + render() { + const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this.props); + fieldProps.isContentActive = this.selectedFunc; + return ( + <div className="schemaRTFCell" style={{ display: 'flex', fontStyle: this.selected ? undefined : 'italic', width: '100%', height: '100%', position: 'relative', color, textDecoration, cursor, pointerEvents }}> + {this.selected ? <FormattedTextBox {...fieldProps} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))} + </div> + ); + } +} +@observer +export class SchemaBoolCell extends React.Component<SchemaTableCellProps> { + @computed get selected() { + const selected: [Doc, number] | undefined = this.props.selectedCell(); + return this.props.isRowActive() && selected?.[0] === this.props.Document && selected[1] === this.props.col; + } + render() { + const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this.props); return ( - <div className="schema-table-cell" style={{ width: this.props.columnWidth }}> - <div className="schemacell-edit-wrapper" style={this.props.isRowActive() ? { cursor: 'text', pointerEvents: 'auto' } : { cursor: 'default', pointerEvents: 'none' }}> - <EditableView - contents={<FieldView {...props} />} - GetValue={() => Field.toKeyValueString(this.props.Document, this.props.fieldKey)} - SetValue={(value: string, shiftDown?: boolean, enterKey?: boolean) => { - if (shiftDown && enterKey) { - this.props.setColumnValues(this.props.fieldKey, value); - } - return KeyValueBox.SetField(this.props.Document, this.props.fieldKey, value); - }} - editing={this.props.isRowActive() ? undefined : false} - /> - </div> + <div className="schemaBoolCell" style={{ display: 'flex', color, textDecoration, cursor, pointerEvents }}> + <input + style={{ marginRight: 4 }} + type="checkbox" + checked={BoolCast(this.props.Document[this.props.fieldKey])} + onChange={undoBatch((value: React.ChangeEvent<HTMLInputElement> | undefined) => { + if ((value?.nativeEvent as any).shiftKey) { + this.props.setColumnValues(this.props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString()); + } + KeyValueBox.SetField(this.props.Document, this.props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString()); + })} + /> + <EditableView + contents={<FieldView {...fieldProps} />} + editing={this.selected ? undefined : false} + GetValue={() => Field.toKeyValueString(this.props.Document, this.props.fieldKey)} + SetValue={undoBatch((value: string, shiftDown?: boolean, enterKey?: boolean) => { + if (shiftDown && enterKey) { + this.props.setColumnValues(this.props.fieldKey.replace(/^_/, ''), value); + } + return KeyValueBox.SetField(this.props.Document, this.props.fieldKey.replace(/^_/, ''), value); + })} + /> </div> ); } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 7107707d1..3bdd2bf6e 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -47,6 +47,7 @@ import { LinkAnchorBox } from './LinkAnchorBox'; import { PresEffect, PresEffectDirection } from './trails'; import { PinProps, PresBox } from './trails/PresBox'; import React = require('react'); +import { InkingStroke } from '../InkingStroke'; const { Howl } = require('howler'); interface Window { @@ -215,7 +216,6 @@ export interface DocumentViewProps extends DocumentViewSharedProps { radialMenu?: String[]; LayoutTemplateString?: string; dontCenter?: 'x' | 'y' | 'xy'; - dontScaleFilter?: (doc: Doc) => boolean; // decides whether a document can be scaled to fit its container vs native size with scrolling NativeWidth?: () => number; NativeHeight?: () => number; NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal NOTE: Must also be added to FieldViewProps @@ -262,7 +262,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps @observable _animateScalingTo = 0; public get animateScaleTime() { - return this._animateScaleTime ?? 300; + return this._animateScaleTime ?? 100; } public get displayName() { return 'DocumentView(' + this.props.Document.title + ')'; @@ -295,8 +295,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Decorations + (this.props.isSelected() ? ':selected' : '')); } @computed get backgroundBoxColor() { - const thumb = ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb)); - return thumb ? undefined : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor + ':box'); + return this.thumbShown() ? undefined : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor + ':box'); } @computed get docContents() { return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.DocContents); @@ -413,6 +412,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps setTimeout(() => this._titleRef.current?.setIsFocused(true)); // use timeout in case title wasn't shown to allow re-render so that titleref will be defined }; + public static addDocTabFunc: (doc: Doc, location: OpenWhere) => boolean = returnFalse; + onClick = action((e: React.MouseEvent | React.PointerEvent) => { if (!this.Document.ignoreClick && this.pointerEvents !== 'none' && this.props.renderDepth >= 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) { let stopPropagate = true; @@ -446,24 +447,33 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps let clickFunc: undefined | (() => any); if (!this.disableClickScriptFunc && this.onClickHandler?.script) { const { clientX, clientY, shiftKey, altKey, metaKey } = e; - const func = () => - this.onClickHandler?.script.run( - { - this: this.layoutDoc, - self: this.rootDoc, - _readOnly_: false, - scriptContext: this.props.scriptContext, - documentView: this.props.DocumentView(), - clientX, - clientY, - shiftKey, - altKey, - metaKey, - }, - console.log - ).result?.select === true - ? this.props.select(false) - : ''; + const func = () => { + // replace default add doc func with this view's add doc func. + // to allow override behaviors for how to display links to undisplayed documents. + // e.g., if this document is part of a labeled 'lightbox' container, then documents will be shown in place + // instead of in the global lightbox + const oldFunc = DocumentViewInternal.addDocTabFunc; + DocumentViewInternal.addDocTabFunc = this.props.addDocTab; + const res = + this.onClickHandler?.script.run( + { + this: this.layoutDoc, + self: this.rootDoc, + _readOnly_: false, + scriptContext: this.props.scriptContext, + documentView: this.props.DocumentView(), + clientX, + clientY, + shiftKey, + altKey, + metaKey, + }, + console.log + ).result?.select === true + ? this.props.select(false) + : ''; + DocumentViewInternal.addDocTabFunc = oldFunc; + }; clickFunc = () => (this.props.Document.dontUndo ? func() : UndoManager.RunInBatch(func, 'on click')); } else { // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplateForField implies we're clicking on part of a template instance and we want to select the whole template, not the part @@ -473,7 +483,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps preventDefault = false; } - this._singleClickFunc = clickFunc ?? (() => (this._componentView?.select ?? this.props.select)(e.ctrlKey || e.metaKey, e.shiftKey)); + this._singleClickFunc = clickFunc ?? (() => this._componentView?.select?.(e.ctrlKey || e.metaKey, e.shiftKey) ?? this.props.select(e.ctrlKey || e.metaKey || e.shiftKey)); const waitFordblclick = this.props.waitForDoubleClickToClick?.() ?? this.Document.waitForDoubleClickToClick; if ((clickFunc && waitFordblclick !== 'never') || waitFordblclick === 'always') { this._doubleClickTimeout && clearTimeout(this._doubleClickTimeout); @@ -693,7 +703,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps !Doc.noviceMode && templateDoc && appearanceItems.push({ description: 'Open Template ', event: () => this.props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' }); !appearance && appearanceItems.length && cm.addItem({ description: 'UI Controls...', subitems: appearanceItems, icon: 'compass' }); - if (!Doc.IsSystem(this.rootDoc) && this.rootDoc._viewType !== CollectionViewType.Docking && this.props.docViewPath().lastElement()?.rootDoc?._viewType !== CollectionViewType.Tree) { + if (!Doc.IsSystem(this.rootDoc) && this.rootDoc.type !== DocumentType.PRES && ![CollectionViewType.Docking, CollectionViewType.Tree].includes(this.rootDoc._viewType as any)) { const existingOnClick = cm.findByDescription('OnClick...'); const onClicks: ContextMenuProps[] = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : []; @@ -707,7 +717,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps event: undoBatch(action(() => (this.rootDoc._raiseWhenDragged = this.rootDoc._raiseWhenDragged === undefined ? false : undefined))), icon: 'hand-point-up', }); - !zorders && cm.addItem({ description: 'ZOrder...', noexpand: true, subitems: zorderItems, icon: 'compass' }); + !zorders && cm.addItem({ description: 'Z Order...', addDivider: true, noexpand: true, subitems: zorderItems, icon: 'compass' }); } onClicks.push({ description: 'Enter Portal', event: this.makeIntoPortal, icon: 'window-restore' }); @@ -720,11 +730,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps onClicks.push({ description: this.onClickHandler ? 'Remove Click Behavior' : 'Follow Link', event: () => this.toggleFollowLink(false, false), icon: 'link' }); onClicks.push({ description: 'Edit onClick Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' }); - !existingOnClick && cm.addItem({ description: 'OnClick...', addDivider: true, noexpand: true, subitems: onClicks, icon: 'mouse-pointer' }); + !existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' }); } else if (LinkManager.Links(this.Document).length) { onClicks.push({ description: 'Select on Click', event: () => this.noOnClick(), icon: 'link' }); onClicks.push({ description: 'Follow Link on Click', event: () => this.followLinkOnClick(), icon: 'link' }); - !existingOnClick && cm.addItem({ description: 'OnClick...', addDivider: true, subitems: onClicks, icon: 'mouse-pointer' }); + !existingOnClick && cm.addItem({ description: 'OnClick...', subitems: onClicks, icon: 'mouse-pointer' }); } } @@ -755,7 +765,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps !more && moreItems.length && cm.addItem({ description: 'More...', subitems: moreItems, icon: 'compass' }); } const constantItems: ContextMenuProps[] = []; - + constantItems.push({ description: 'Show Metadata', event: () => this.props.addDocTab(this.props.Document, (OpenWhere.addRight.toString() + 'KeyValue') as OpenWhere), icon: 'layer-group' }); if (!Doc.IsSystem(this.rootDoc)) { constantItems.push({ description: 'Export as Zip file', icon: 'download', event: async () => Doc.Zip(this.props.Document) }); constantItems.push({ description: 'Import Zipped file', icon: 'upload', event: ({ x, y }) => this.importDocument() }); @@ -764,11 +774,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps // need option to gray out menu items ... preferably with a '?' that explains why they're grayed out (eg., no permissions) constantItems.push({ description: 'Close', event: this.deleteClicked, icon: 'times' }); } - cm.addItem({ description: 'General...', noexpand: false, subitems: constantItems, icon: 'question' }); } + cm.addItem({ description: 'General...', noexpand: !Doc.IsSystem(this.rootDoc), subitems: constantItems, icon: 'question' }); + const help = cm.findByDescription('Help...'); const helpItems: ContextMenuProps[] = help && 'subitems' in help ? help.subitems : []; - helpItems.push({ description: 'Show Metadata', event: () => this.props.addDocTab(this.props.Document, (OpenWhere.addRight.toString() + 'KeyValue') as OpenWhere), icon: 'layer-group' }); !Doc.noviceMode && helpItems.push({ description: 'Text Shortcuts Ctrl+/', event: () => this.props.addDocTab(Docs.Create.PdfDocument('/assets/cheat-sheet.pdf', { _width: 300, _height: 300 }), OpenWhere.addRight), icon: 'keyboard' }); !Doc.noviceMode && helpItems.push({ description: 'Print Document in Console', event: () => console.log(this.props.Document), icon: 'hand-point-right' }); !Doc.noviceMode && helpItems.push({ description: 'Print DataDoc in Console', event: () => console.log(this.props.Document[DataSym]), icon: 'hand-point-right' }); @@ -809,13 +819,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps if (documentationDescription && documentationLink) { helpItems.push({ description: documentationDescription, - event: () => { - window.open(documentationLink, '_blank'); - }, + event: () => window.open(documentationLink, '_blank'), icon: 'book', }); } - if (!help) cm.addItem({ description: 'Help...', noexpand: true, subitems: helpItems, icon: 'question' }); + if (!help) cm.addItem({ description: 'Help...', noexpand: !Doc.noviceMode, subitems: helpItems, icon: 'question' }); else cm.moveAfter(help); e?.stopPropagation(); // DocumentViews should stop propagation of this event @@ -858,14 +866,16 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps ? true : false; }; + docFilters = () => [...this.props.docFilters(), ...StrListCast(this.layoutDoc.docFilters)]; contentPointerEvents = () => (!this.disableClickScriptFunc && this.onClickHandler ? 'none' : this.pointerEvents); @computed get contents() { TraceMobx(); + const isInk = StrCast(this.layoutDoc.layout).includes(InkingStroke.name); return ( <div className="documentView-contentsView" style={{ - pointerEvents: (this.pointerEvents === 'visiblePainted' ? 'none' : this.pointerEvents) ?? 'all', + pointerEvents: (isInk ? 'none' : this.pointerEvents) ?? 'all', height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined, }}> {!this._retryThumb || !this.thumbShown() ? null : ( @@ -888,6 +898,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps docViewPath={this.props.viewPath} thumbShown={this.thumbShown} setContentView={this.setContentView} + docFilters={this.docFilters} NativeDimScaling={this.props.NativeDimScaling} PanelHeight={this.panelHeight} setHeight={!this.props.suppressSetHeight ? this.setHeight : undefined} @@ -1318,9 +1329,6 @@ export class DocumentView extends React.Component<DocumentViewProps> { const hideCount = this.props.renderDepth === -1 || SnappingManager.GetIsDragging() || (this.isSelected() && this.props.renderDepth) || !this._isHovering || this.hideLinkButton; return hideCount ? null : <DocumentLinksButton View={this} scaling={this.scaleToScreenSpace} OnHover={true} Bottom={this.topMost} ShowCount={true} />; } - @computed get hidden() { - return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Hidden); - } @computed get docViewPath(): DocumentView[] { return this.props.docViewPath ? [...this.props.docViewPath(), this] : [this]; } @@ -1334,7 +1342,7 @@ export class DocumentView extends React.Component<DocumentViewProps> { return this.docView?._componentView?.reverseNativeScaling?.() ? 0 : returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, !this.fitWidth)); } @computed get shouldNotScale() { - return (this.fitWidth && !this.nativeWidth) || this.props.dontScaleFilter?.(this.Document) || [CollectionViewType.Docking].includes(this.Document._viewType as any); + return (this.fitWidth && !this.nativeWidth) || [CollectionViewType.Docking].includes(this.Document._viewType as any); } @computed get effectiveNativeWidth() { return this.shouldNotScale ? 0 : this.nativeWidth || NumCast(this.layoutDoc.width); @@ -1497,7 +1505,7 @@ export class DocumentView extends React.Component<DocumentViewProps> { const xshift = Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined; const yshift = Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined; - return this.hidden ? null : ( + return ( <div className="contentFittingDocumentView" onPointerEnter={action(() => (this._isHovering = true))} onPointerLeave={action(() => (this._isHovering = false))}> {!this.props.Document || !this.props.PanelWidth() ? null : ( <div diff --git a/src/client/views/nodes/FilterBox.scss b/src/client/views/nodes/FilterBox.scss deleted file mode 100644 index 7f907c8d4..000000000 --- a/src/client/views/nodes/FilterBox.scss +++ /dev/null @@ -1,189 +0,0 @@ -.filterBox-flyout { - display: block; - text-align: left; - font-weight: 100; - - .filterBox-flyout-facet { - background-color: white; - text-align: left; - display: inline-block; - position: relative; - width: 100%; - - .filterBox-flyout-facet-check { - margin-right: 6px; - } - } -} - -.filter-bookmark { - //display: flex; - - .filter-bookmark-icon { - float: right; - margin-right: 10px; - margin-top: 7px; - } -} - -// .filterBox-bottom { -// // position: fixed; -// // bottom: 0; -// // width: 100%; -// } - -.filterBox-select { - // width: 90%; - margin-top: 5px; - // margin-bottom: 15px; -} - -.filterBox-saveBookmark { - background-color: #e9e9e9; - border-radius: 11px; - padding-left: 8px; - padding-right: 8px; - padding-top: 5px; - padding-bottom: 5px; - margin: 8px; - display: flex; - font-size: 11px; - cursor: pointer; - - &:hover { - background-color: white; - } - - .filterBox-saveBookmark-icon { - margin-right: 6px; - margin-top: 4px; - margin-left: 2px; - } -} - -.filterBox-select-scope, -.filterBox-select-bool, -.filterBox-addWrapper, -.filterBox-select-matched, -.filterBox-saveWrapper { - font-size: 10px; - justify-content: center; - justify-items: center; - padding-bottom: 10px; - display: flex; -} - -.filterBox-addWrapper { - font-size: 11px; - width: 100%; -} - -.filterBox-saveWrapper { - width: 100%; -} - -// .filterBox-top { -// padding-bottom: 20px; -// border-bottom: 2px solid black; -// position: fixed; -// top: 0; -// width: 100%; -// } - -.filterBox-select-scope { - padding-bottom: 20px; - border-bottom: 2px solid black; -} - -.filterBox-title { - font-size: 15; - // border: 2px solid black; - width: 100%; - align-self: center; - text-align: center; - background-color: #d3d3d3; -} - -.filterBox-select-bool { - margin-top: 6px; -} - -.filterBox-select-text { - margin-right: 8px; - margin-left: 8px; - margin-top: 3px; -} - -.filterBox-select-box { - margin-right: 2px; - font-size: 30px; - border: 0; - background: transparent; -} - -.filterBox-selection { - border-radius: 6px; - border: none; - background-color: #e9e9e9; - padding: 2px; - - &:hover { - background-color: white; - } -} - -.filterBox-addFilter { - width: 120px; - background-color: #e9e9e9; - border-radius: 12px; - padding: 5px; - margin: 5px; - display: flex; - text-align: center; - justify-content: center; - - &:hover { - background-color: white; - } -} - -.filterBox-treeView { - display: flex; - flex-direction: column; - width: 200px; - position: absolute; - right: 0; - top: 0; - z-index: 1; - background-color: #9f9f9f; - - .filterBox-tree { - z-index: 0; - } - - .filterBox-addfacet { - display: inline-block; - width: 200px; - height: 30px; - text-align: left; - - .filterBox-addFacetButton { - display: flex; - margin: auto; - cursor: pointer; - } - - > div, - > div > div { - width: 100%; - height: 100%; - } - } - - .filterBox-tree { - display: inline-block; - width: 100%; - margin-bottom: 10px; - //height: calc(100% - 30px); - } -} diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx deleted file mode 100644 index e69de29bb..000000000 --- a/src/client/views/nodes/FilterBox.tsx +++ /dev/null diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss index 6359a9491..29943e156 100644 --- a/src/client/views/nodes/ImageBox.scss +++ b/src/client/views/nodes/ImageBox.scss @@ -2,7 +2,9 @@ border-radius: inherit; width: 100%; height: 100%; - position: relative; + position: absolute; + top: 0; + left: 0; transform-origin: top left; .imageBox-annotationLayer { @@ -119,6 +121,14 @@ } } } +.imageBox-alternateDropTarget { + position: absolute; + color: white; + background: black; + right: 0; + bottom: 0; + z-index: 2; +} .imageBox-fader img { position: absolute; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 7c98aa6e4..c9be10d3a 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -1,3 +1,5 @@ +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 { extname } from 'path'; @@ -8,7 +10,7 @@ import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; import { createSchema } from '../../../fields/Schema'; import { ComputedField } from '../../../fields/ScriptField'; -import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types'; +import { Cast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; import { DashColor, emptyFunction, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils'; @@ -19,6 +21,7 @@ import { DocumentType } from '../../documents/DocumentTypes'; import { Networking } from '../../Network'; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager } from '../../util/DragManager'; +import { SnappingManager } from '../../util/SnappingManager'; import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from '../../views/ContextMenu'; import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; @@ -57,6 +60,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp private _dropDisposer?: DragManager.DragDropDisposer; private _disposers: { [name: string]: IReactionDisposer } = {}; private _getAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = () => undefined; + private _overlayIconRef = React.createRef<HTMLDivElement>(); @observable _curSuffix = ''; @observable _uploadIcon = uploadIcons.idle; @@ -131,10 +135,16 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp @action drop = (e: Event, de: DragManager.DropEvent) => { if (de.complete.docDragData) { - if (de.metaKey) { + const targetIsBullseye = (ele: HTMLElement): boolean => { + if (!ele) return false; + if (ele === this._overlayIconRef.current) return true; + return targetIsBullseye(ele.parentElement as HTMLElement); + }; + if (de.metaKey || targetIsBullseye(e.target as HTMLElement)) { de.complete.docDragData.droppedDocuments.forEach( action((drop: Doc) => { Doc.AddDocToList(this.dataDoc, this.fieldKey + '-alternates', drop); + this.rootDoc[this.fieldKey + '-usePath'] = 'alternate:hover'; e.stopPropagation(); }) ); @@ -154,8 +164,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp @undoBatch resolution = () => (this.layoutDoc._showFullRes = !this.layoutDoc._showFullRes); - @undoBatch - setUseAlt = () => (this.layoutDoc[this.fieldKey + '-useAlt'] = !this.layoutDoc[this.fieldKey + '-useAlt']); @undoBatch setNativeSize = action(() => { @@ -239,7 +247,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp funcs.push({ description: 'Rotate Clockwise 90', event: this.rotate, icon: 'redo-alt' }); funcs.push({ description: `Show ${this.layoutDoc._showFullRes ? 'Dynamic Res' : 'Full Res'}`, event: this.resolution, icon: 'expand' }); funcs.push({ description: 'Set Native Pixel Size', event: this.setNativeSize, icon: 'expand-arrows-alt' }); - funcs.push({ description: `${this.layoutDoc[this.fieldKey + '-useAlt'] ? 'Show Alternate' : 'Show Primary'}`, event: this.setUseAlt, icon: 'image' }); funcs.push({ description: 'Copy path', event: () => Utils.CopyText(this.choosePath(field.url)), icon: 'copy' }); if (!Doc.noviceMode) { funcs.push({ description: 'Export to Google Photos', event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: 'caret-square-right' }); @@ -352,10 +359,45 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp @computed get nativeSize() { TraceMobx(); const nativeWidth = NumCast(this.dataDoc[this.fieldKey + '-nativeWidth'], NumCast(this.layoutDoc[this.fieldKey + '-nativeWidth'], 500)); - const nativeHeight = NumCast(this.dataDoc[this.fieldKey + '-nativeHeight'], NumCast(this.layoutDoc[this.fieldKey + '-nativeHeight'], 1)); + const nativeHeight = NumCast(this.dataDoc[this.fieldKey + '-nativeHeight'], NumCast(this.layoutDoc[this.fieldKey + '-nativeHeight'], 500)); const nativeOrientation = NumCast(this.dataDoc[this.fieldKey + '-nativeOrientation'], 1); return { nativeWidth, nativeHeight, nativeOrientation }; } + @computed get overlayImageIcon() { + const usePath = this.rootDoc[`_${this.fieldKey}-usePath`]; + return ( + <Tooltip + title={ + <div className="dash-tooltip"> + toggle between + <span style={{ color: usePath === undefined ? 'black' : undefined }}> + <em> primary, </em> + </span> + <span style={{ color: usePath === 'alternate' ? 'black' : undefined }}> + <em>alternate, </em> + </span> + and show + <span style={{ color: usePath === 'alternate:hover' ? 'black' : undefined }}> + <em> alternate on hover</em> + </span> + </div> + }> + <div + className="imageBox-alternateDropTarget" + ref={this._overlayIconRef} + onPointerDown={e => setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => (this.rootDoc[`_${this.fieldKey}-usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined))} + style={{ + display: (SnappingManager.GetIsDragging() && DragManager.DocDragData?.canEmbed) || DocListCast(this.dataDoc[this.fieldKey + '-alternates']).length ? 'block' : 'none', + width: 'min(10%, 25px)', + height: 'min(10%, 25px)', + background: usePath === undefined ? 'white' : usePath === 'alternate' ? 'black' : 'gray', + color: usePath === undefined ? 'black' : 'white', + }}> + <FontAwesomeIcon icon="turn-up" size="lg" /> + </div> + </Tooltip> + ); + } @computed get paths() { const field = Cast(this.dataDoc[this.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc @@ -365,7 +407,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp .filter(url => url) .map(url => this.choosePath(url)); // access the primary layout data of the alternate documents const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths; - return paths.length ? paths : [Utils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png')]; + return paths.length ? paths : [Utils.CorsProxy('https://cs.brown.edu/~bcz/noImage.png')]; } @observable _isHovering = false; // flag to switch between primary and alternate images on hover @@ -389,15 +431,14 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp transformOrigin = 'right top'; transform = `translate(-100%, 0%) rotate(${rotation}deg) scale(${aspect})`; } + const usePath = this.rootDoc[`_${this.fieldKey}-usePath`]; return ( <div className="imageBox-cont" onPointerEnter={action(() => (this._isHovering = true))} onPointerLeave={action(() => (this._isHovering = false))} key={this.layoutDoc[Id]} ref={this.createDropTarget} onPointerDown={this.marqueeDown}> <div className="imageBox-fader" style={{ opacity: backAlpha }}> <img key="paths" src={srcpath} style={{ transform, transformOrigin }} draggable={false} width={nativeWidth} /> {fadepath === srcpath ? null : ( - <div - className={`imageBox-fadeBlocker${(this._isHovering && this.layoutDoc[this.fieldKey + '-useAlt'] === undefined) || BoolCast(this.layoutDoc[this.fieldKey + '-useAlt']) ? '-hover' : ''}`} - style={{ transition: StrCast(this.layoutDoc.viewTransition, 'opacity 1000ms') }}> + <div className={`imageBox-fadeBlocker${(this._isHovering && usePath === 'alternate:hover') || usePath === 'alternate' ? '-hover' : ''}`} style={{ transition: StrCast(this.layoutDoc.viewTransition, 'opacity 1000ms') }}> <img className="imageBox-fadeaway" key="fadeaway" src={fadepath} style={{ transform, transformOrigin }} draggable={false} width={nativeWidth} /> </div> )} @@ -405,6 +446,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp {!Doc.noviceMode && this.considerDownloadIcon} {this.considerGooglePhotosLink()} <FaceRectangles document={this.dataDoc} color={'#0000FF'} backgroundColor={'#0000FF'} /> + {this.overlayImageIcon} </div> ); } @@ -463,6 +505,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp } })} style={{ + display: !SnappingManager.GetIsDragging() && this.props.thumbShown?.() ? 'none' : undefined, width: this.props.PanelWidth() ? undefined : `100%`, height: this.props.PanelWidth() ? undefined : `100%`, pointerEvents: this.layoutDoc._lockedPosition ? 'none' : undefined, diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 57018fb93..e317de11e 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -20,14 +20,12 @@ import { ImageBox } from './ImageBox'; import './KeyValueBox.scss'; import { KeyValuePair } from './KeyValuePair'; import React = require('react'); -import e = require('express'); export type KVPScript = { script: CompiledScript; type: 'computed' | 'script' | false; onDelegate: boolean; }; - @observer export class KeyValueBox extends React.Component<FieldViewProps> { public static LayoutString() { @@ -75,7 +73,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> { value = eq ? value.substring(1) : value; const dubEq = value.startsWith(':=') ? 'computed' : value.startsWith('$=') ? 'script' : false; value = dubEq ? value.substring(2) : value; - const options: ScriptOptions = { addReturn: true, typecheck: false, params: { this: Doc.name, self: Doc.name, _last_: 'any', _readOnly_: 'boolean' }, editable: false }; + const options: ScriptOptions = { addReturn: true, typecheck: false, params: { this: Doc.name, self: Doc.name, _last_: 'any', _readOnly_: 'boolean' }, editable: true }; if (dubEq) options.typecheck = false; const script = CompileScript(value, options); return !script.compiled ? undefined : { script, type: dubEq, onDelegate: eq }; @@ -98,7 +96,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> { } field = res.result; } - if (Field.IsField(field, true)) { + if (Field.IsField(field, true) && (key !== 'proto' || field !== target)) { target[key] = field; return true; } diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 8f87b9e08..6aa04e356 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -586,6 +586,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps }}> <PDFViewer {...this.props} + sidebarAddDoc={this.sidebarAddDocument} rootDoc={this.rootDoc} addDocTab={this.sidebarAddDocTab} layoutDoc={this.layoutDoc} diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx index 0ff7c4292..f406ffbea 100644 --- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx +++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx @@ -1,58 +1,64 @@ -import { action, observable } from "mobx"; -import { observer } from "mobx-react"; -import * as React from "react"; -import { VideoField } from "../../../../fields/URLField"; -import { Upload } from "../../../../server/SharedMediaTypes"; -import { ViewBoxBaseComponent } from "../../DocComponent"; -import { FieldView } from "../FieldView"; -import { VideoBox } from "../VideoBox"; +import { action, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { VideoField } from '../../../../fields/URLField'; +import { Upload } from '../../../../server/SharedMediaTypes'; +import { ViewBoxBaseComponent } from '../../DocComponent'; +import { FieldView } from '../FieldView'; +import { VideoBox } from '../VideoBox'; import { RecordingView } from './RecordingView'; -import { DocumentType } from "../../../documents/DocumentTypes"; -import { Presentation } from "../../../util/TrackMovements"; -import { Doc } from "../../../../fields/Doc"; -import { Id } from "../../../../fields/FieldSymbols"; - +import { DocumentType } from '../../../documents/DocumentTypes'; +import { Presentation } from '../../../util/TrackMovements'; +import { Doc } from '../../../../fields/Doc'; +import { Id } from '../../../../fields/FieldSymbols'; @observer export class RecordingBox extends ViewBoxBaseComponent() { + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(RecordingBox, fieldKey); + } - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(RecordingBox, fieldKey); } - - private _ref: React.RefObject<HTMLDivElement> = React.createRef(); + private _ref: React.RefObject<HTMLDivElement> = React.createRef(); - constructor(props: any) { + constructor(props: any) { super(props); - } - - componentDidMount() { - Doc.SetNativeWidth(this.dataDoc, 1280); - Doc.SetNativeHeight(this.dataDoc, 720); - } - - @observable result: Upload.AccessPathInfo | undefined = undefined - @observable videoDuration: number | undefined = undefined - - @action - setVideoDuration = (duration: number) => { - this.videoDuration = duration - } - - @action - setResult = (info: Upload.AccessPathInfo, presentation?: Presentation) => { - this.result = info - this.dataDoc.type = DocumentType.VID; - this.dataDoc[this.fieldKey + "-duration"] = this.videoDuration; - - this.dataDoc.layout = VideoBox.LayoutString(this.fieldKey); - this.dataDoc[this.props.fieldKey] = new VideoField(this.result.accessPaths.client); - this.dataDoc[this.fieldKey + "-recorded"] = true; - // stringify the presentation and store it - presentation?.movements && (this.dataDoc[this.fieldKey + "-presentation"] = JSON.stringify(presentation)); - } - - render() { - return <div className="recordingBox" ref={this._ref}> - {!this.result && <RecordingView setResult={this.setResult} setDuration={this.setVideoDuration} id={this.rootDoc.proto?.[Id] || ''} />} - </div>; - } + } + + componentDidMount() { + Doc.SetNativeWidth(this.dataDoc, 1280); + Doc.SetNativeHeight(this.dataDoc, 720); + } + + @observable result: Upload.AccessPathInfo | undefined = undefined; + @observable videoDuration: number | undefined = undefined; + + @action + setVideoDuration = (duration: number) => { + this.videoDuration = duration; + }; + + @action + setResult = (info: Upload.AccessPathInfo, presentation?: Presentation) => { + this.result = info; + this.dataDoc.type = DocumentType.VID; + this.dataDoc[this.fieldKey + '-duration'] = this.videoDuration; + + this.dataDoc.layout = VideoBox.LayoutString(this.fieldKey); + this.dataDoc[this.props.fieldKey] = new VideoField(this.result.accessPaths.client); + this.dataDoc[this.fieldKey + '-recorded'] = true; + // stringify the presentation and store it + if (presentation?.movements) { + const presCopy = { ...presentation }; + presCopy.movements = presentation.movements.map(movement => ({ ...movement, doc: movement.doc[Id] })) as any; + this.dataDoc[this.fieldKey + '-presentation'] = JSON.stringify(presCopy); + } + }; + + render() { + return ( + <div className="recordingBox" ref={this._ref}> + {!this.result && <RecordingView setResult={this.setResult} setDuration={this.setVideoDuration} id={this.rootDoc.proto?.[Id] || ''} />} + </div> + ); + } } diff --git a/src/client/views/nodes/RecordingBox/RecordingView.tsx b/src/client/views/nodes/RecordingBox/RecordingView.tsx index 6efe62e0b..424ebc384 100644 --- a/src/client/views/nodes/RecordingBox/RecordingView.tsx +++ b/src/client/views/nodes/RecordingBox/RecordingView.tsx @@ -1,14 +1,14 @@ import * as React from 'react'; -import './RecordingView.scss'; import { useEffect, useRef, useState } from 'react'; -import { ProgressBar } from './ProgressBar'; -import { MdBackspace } from 'react-icons/md'; -import { FaCheckCircle } from 'react-icons/fa'; import { IconContext } from 'react-icons'; -import { Networking } from '../../../Network'; +import { FaCheckCircle } from 'react-icons/fa'; +import { MdBackspace } from 'react-icons/md'; import { Upload } from '../../../../server/SharedMediaTypes'; import { returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils'; +import { Networking } from '../../../Network'; import { Presentation, TrackMovements } from '../../../util/TrackMovements'; +import { ProgressBar } from './ProgressBar'; +import './RecordingView.scss'; export interface MediaSegment { videoChunks: any[]; diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss index 6f578a9fc..75847c100 100644 --- a/src/client/views/nodes/WebBox.scss +++ b/src/client/views/nodes/WebBox.scss @@ -183,6 +183,7 @@ height: 100%; position: absolute; top: 0; + left: 0; body { ::selection { color: white; diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index ef3aa1f47..e05b48c0b 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -28,6 +28,7 @@ import { LightboxView } from '../LightboxView'; import { MarqueeAnnotator } from '../MarqueeAnnotator'; import { AnchorMenu } from '../pdf/AnchorMenu'; import { Annotation } from '../pdf/Annotation'; +import { GPTPopup } from '../pdf/GPTPopup/GPTPopup'; import { SidebarAnnos } from '../SidebarAnnos'; import { StyleProp } from '../StyleProvider'; import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from './DocumentView'; @@ -366,8 +367,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps const scale = (this.props.NativeDimScaling?.() || 1) * mainContBounds.scale; const sel = this._iframe.contentWindow.getSelection(); if (sel) { + this._selectionText = sel.toString(); + AnchorMenu.Instance.setSelectedText(sel.toString()); this._textAnnotationCreator = () => this.createTextAnnotation(sel, !sel.isCollapsed ? sel.getRangeAt(0) : undefined); AnchorMenu.Instance.jumpTo(e.clientX * scale + mainContBounds.translateX, e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._scrollTop) * scale); + // Changing which document to add the annotation to (the currently selected WebBox) + GPTPopup.Instance.setSidebarId(`${this.props.fieldKey}-${this._urlHash}-sidebar`); + GPTPopup.Instance.addDoc = this.sidebarAddDocument; } } }; @@ -785,6 +791,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => { + console.log(annotationKey); (doc instanceof Doc ? [doc] : doc).forEach(doc => (doc.webUrl = this._url)); return this.addDocument(doc, annotationKey); }; diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 1a75a7e76..f04678d8b 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -62,6 +62,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(FontIconBox, fieldKey); } + @observable noTooltip = false; showTemplate = (): void => { const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null); dragFactory && this.props.addDocTab(dragFactory, OpenWhere.addRight); @@ -166,6 +167,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() { onPointerDown={e => e.stopPropagation()} onClick={action(() => { this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen; + this.noTooltip = this.rootDoc.dropDownOpen; Doc.UnBrushAllDocs(); })}> {checkResult} @@ -206,6 +208,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() { }} onClick={action(() => { this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen; + this.noTooltip = this.rootDoc.dropDownOpen; Doc.UnBrushAllDocs(); })}> <input style={{ width: 30 }} className="button-input" type="number" value={checkResult} onChange={undoBatch(action(e => setValue(Number(e.target.value))))} /> @@ -224,6 +227,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() { onClick={e => { e.stopPropagation(); this.rootDoc.dropDownOpen = false; + this.noTooltip = false; Doc.UnBrushAllDocs(); }} /> @@ -249,6 +253,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() { style={{ color: color, backgroundColor: backgroundColor, borderBottomLeftRadius: this.dropdown ? 0 : undefined }} onClick={action(() => { this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen; + this.noTooltip = this.rootDoc.dropDownOpen; Doc.UnBrushAllDocs(); })}> {this.Icon(color)} @@ -333,6 +338,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() { dropdown ? () => { this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen; + this.noTooltip = this.rootDoc.dropDownOpen; Doc.UnBrushAllDocs(); } : undefined @@ -355,6 +361,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() { onClick={e => { e.stopPropagation(); this.rootDoc.dropDownOpen = false; + this.noTooltip = false; Doc.UnBrushAllDocs(); }} /> @@ -400,6 +407,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() { style={{ color: color, borderBottomLeftRadius: this.dropdown ? 0 : undefined }} onClick={action(e => { this.colorPickerClosed = !this.colorPickerClosed; + this.noTooltip = !this.colorPickerClosed; setTimeout(() => Doc.UnBrushAllDocs()); e.stopPropagation(); })} @@ -419,6 +427,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() { e.preventDefault(); e.stopPropagation(); this.colorPickerClosed = true; + this.noTooltip = false; Doc.UnBrushAllDocs(); })} /> @@ -516,10 +525,10 @@ export class FontIconBox extends DocComponent<ButtonProps>() { // prettier-ignore switch (this.type) { - case ButtonType.DropdownList: return this.dropdownListButton; - case ButtonType.ColorButton: return this.colorButton; - case ButtonType.NumberButton: return this.numberButton; case ButtonType.EditableText: return this.editableText; + case ButtonType.DropdownList: button = this.dropdownListButton; break; + case ButtonType.ColorButton: button = this.colorButton; break; + case ButtonType.NumberButton: button = this.numberButton; break; case ButtonType.DropdownButton: button = this.dropdownButton; break; case ButtonType.ToggleButton: button = this.toggleButton; break; case ButtonType.TextButton: @@ -552,7 +561,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() { break; } - return !this.layoutDoc.toolTip ? button : <Tooltip title={<div className="dash-tooltip">{StrCast(this.layoutDoc.toolTip)}</div>}>{button}</Tooltip>; + return !this.layoutDoc.toolTip || this.noTooltip ? button : <Tooltip title={<div className="dash-tooltip">{StrCast(this.layoutDoc.toolTip)}</div>}>{button}</Tooltip>; } } @@ -565,7 +574,12 @@ ScriptingGlobals.add(function setView(view: string) { // toggle: Set overlay status of selected document ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: boolean) { const selectedViews = SelectionManager.Views(); - if (selectedViews.length) { + if (Doc.ActiveTool !== InkTool.None) { + if (checkResult) { + return ActiveFillColor(); + } + SetActiveFillColor(color ?? 'transparent'); + } else if (selectedViews.length) { if (checkResult) { const selView = selectedViews.lastElement(); const layoutFrameNumber = Cast(selView.props.docViewPath().lastElement()?.rootDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values @@ -610,16 +624,16 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) { selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log('[FontIconBox.tsx] toggleOverlay failed'); }); -ScriptingGlobals.add(function showFreeform(attr: 'grid' | 'snap lines' | 'clusters' | 'arrange' | 'viewAll', checkResult?: boolean) { +ScriptingGlobals.add(function showFreeform(attr: 'grid' | 'snapline' | 'clusters' | 'arrange' | 'viewAll', checkResult?: boolean) { const selected = SelectionManager.Docs().lastElement(); // prettier-ignore - const map: Map<'grid' | 'snap lines' | 'clusters' | 'arrange'| 'viewAll', { undo: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc) => void;}> = new Map([ + const map: Map<'grid' | 'snapline' | 'clusters' | 'arrange'| 'viewAll', { undo: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc) => void;}> = new Map([ ['grid', { undo: false, checkResult: (doc:Doc) => doc._backgroundGridShow, setDoc: (doc:Doc) => doc._backgroundGridShow = !doc._backgroundGridShow, }], - ['snap lines', { + ['snapline', { undo: false, checkResult: (doc:Doc) => doc.showSnapLines, setDoc: (doc:Doc) => doc._showSnapLines = !doc._showSnapLines, diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx index b31fc01ff..648c579d0 100644 --- a/src/client/views/nodes/formattedText/DashDocView.tsx +++ b/src/client/views/nodes/formattedText/DashDocView.tsx @@ -181,6 +181,8 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> { height: this._height, position: 'absolute', display: 'inline-block', + left: 0, + top: 0, }} onPointerLeave={this.onPointerLeave} onPointerEnter={this.onPointerEnter} diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index c43206629..72e8aedac 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -101,11 +101,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna this._textBoxDoc = this.props.tbox.props.Document; if (this.props.docId) { - DocServer.GetRefField(this.props.docId).then( - action(async dashDoc => { - dashDoc instanceof Doc && (this._dashDoc = dashDoc); - }) - ); + DocServer.GetRefField(this.props.docId).then(action(dashDoc => dashDoc instanceof Doc && (this._dashDoc = dashDoc))); } else { this._dashDoc = this.props.tbox.rootDoc; } diff --git a/src/client/views/nodes/formattedText/FootnoteView.tsx b/src/client/views/nodes/formattedText/FootnoteView.tsx index 531a60297..cf48e1250 100644 --- a/src/client/views/nodes/formattedText/FootnoteView.tsx +++ b/src/client/views/nodes/formattedText/FootnoteView.tsx @@ -83,13 +83,11 @@ export class FootnoteView { }; toggle = () => { - console.log('TOGGLE'); if (this.innerView) this.close(); else this.open(); }; close() { - console.log('CLOSE'); this.innerView?.destroy(); this.innerView = null; this.dom.textContent = ''; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss index cbe0a465d..b5a3c5d84 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -24,6 +24,27 @@ audiotag:hover { transform: scale(2); transform-origin: bottom center; } +.formattedTextBox { + touch-action: none; + background: inherit; + padding: 0; + border-width: 0px; + border-radius: inherit; + border-color: $medium-gray; + box-sizing: border-box; + background-color: inherit; + border-style: solid; + overflow-y: auto; + overflow-x: hidden; + color: inherit; + display: flex; + flex-direction: row; + transition: opacity 1s; + width: 100%; + position: absolute; + top: 0; + left: 0; +} .formattedTextBox-cont { touch-action: none; @@ -51,6 +72,17 @@ audiotag:hover { position: absolute; } } +.formattedTextBox-alternateButton { + align-items: center; + flex-direction: column; + position: absolute; + color: white; + background: black; + right: 0; + bottom: 0; + width: 11; + height: 11; +} .formattedTextBox-outer-selected, .formattedTextBox-outer { @@ -149,6 +181,10 @@ audiotag:hover { } } +.gpt-typing-wrapper { + padding: 10px; +} + // .menuicon { // display: inline-block; // border-right: 1px solid rgba(0, 0, 0, 0.2); @@ -189,16 +225,15 @@ audiotag:hover { } footnote { - display: inline-block; + display: inline-flex; + top: -0.5em; position: relative; cursor: pointer; - - div { - padding: 0 !important; - } + height: 1em; + width: 0.5em; } -footnote::after { +footnote::before { content: counter(prosemirror-footnote); vertical-align: super; font-size: 75%; @@ -212,15 +247,14 @@ footnote::after { .footnote-tooltip { cursor: auto; font-size: 75%; - position: absolute; - left: -30px; - top: calc(100% + 10px); + position: relative; background: silver; - padding: 3px; border-radius: 2px; - max-width: 100px; - min-width: 50px; - width: max-content; + min-width: 100px; + top: 2em; + height: max-content; + left: -1em; + padding: 3px; } .prosemirror-attribution { @@ -235,8 +269,7 @@ footnote::after { border-left-color: transparent; border-right-color: transparent; position: absolute; - top: -5px; - left: 27px; + top: -0.5em; content: ' '; height: 0; width: 0; @@ -730,8 +763,8 @@ footnote::after { cursor: auto; font-size: 75%; position: absolute; - left: -30px; - top: calc(100% + 10px); + // left: -30px; + // top: calc(100% + 10px); background: silver; padding: 3px; border-radius: 2px; @@ -752,8 +785,7 @@ footnote::after { border-left-color: transparent; border-right-color: transparent; position: absolute; - top: -5px; - left: 27px; + top: -0.5em; content: ' '; height: 0; width: 0; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index f5826ef95..eb28ffbd7 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1,5 +1,6 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Tooltip } from '@material-ui/core'; import { isEqual } from 'lodash'; import { action, computed, IReactionDisposer, observable, ObservableSet, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; @@ -22,9 +23,11 @@ import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } fro import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util'; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils'; import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils'; +import { gptAPICall, GPTCallType, gptImageCall } from '../../../apis/gpt/GPT'; import { DocServer } from '../../../DocServer'; import { Docs, DocUtils } from '../../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; +import { Networking } from '../../../Network'; import { DictationManager } from '../../../util/DictationManager'; import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager } from '../../../util/DragManager'; @@ -65,10 +68,9 @@ import { schema } from './schema_rts'; import { SummaryView } from './SummaryView'; import applyDevTools = require('prosemirror-dev-tools'); import React = require('react'); +import { RTFMarkup } from '../../../util/RTFMarkup'; const translateGoogleApi = require('translate-google-api'); - export const GoogleRef = 'googleDocId'; - type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void; @observer @@ -172,6 +174,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps }; } + // State for GPT + @observable + private gptRes: string = ''; + public static PasteOnLoad: ClipboardEvent | undefined; public static SelectOnLoad = ''; public static DontSelectInitialText = false; // whether initial text should be selected or not @@ -845,12 +851,72 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps const options = cm.findByDescription('Options...'); const optionItems = options && 'subitems' in options ? options.subitems : []; - optionItems.push({ description: !this.Document._singleLine ? 'Make Single Line' : 'Make Multi Line', event: () => (this.layoutDoc._singleLine = !this.layoutDoc._singleLine), icon: !this.Document._singleLine ? 'grip-lines' : 'bars' }); + optionItems.push({ description: `Generate Dall-E Image`, event: () => this.generateImage(), icon: 'star' }); + optionItems.push({ description: `Ask GPT-3`, event: () => this.askGPT(), icon: 'lightbulb' }); + optionItems.push({ + description: !this.Document._singleLine ? 'Create New Doc on Carriage Return' : 'Allow Carriage Returns', + event: () => (this.layoutDoc._singleLine = !this.layoutDoc._singleLine), + icon: !this.Document._singleLine ? 'grip-lines' : 'bars', + }); optionItems.push({ description: `${this.Document._autoHeight ? 'Lock' : 'Auto'} Height`, event: () => (this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight), icon: this.Document._autoHeight ? 'lock' : 'unlock' }); + optionItems.push({ description: `show markdown options`, event: RTFMarkup.Instance.open, icon: 'text' }); !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' }); this._downX = this._downY = Number.NaN; }; + animateRes = (resIndex: number) => { + if (resIndex < this.gptRes.length) { + this.dataDoc.text = (this.dataDoc.text as RichTextField)?.Text + this.gptRes[resIndex]; + setTimeout(() => { + this.animateRes(resIndex + 1); + }, 20); + } + }; + + askGPT = action(async () => { + try { + let res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION); + if (res) { + this.gptRes = res; + this.animateRes(0); + } + } catch (err) { + console.log(err); + this.dataDoc.text = (this.dataDoc.text as RichTextField)?.Text + 'Something went wrong'; + } + }); + + generateImage = async () => { + console.log('Generate image from text: ', (this.dataDoc.text as RichTextField)?.Text); + try { + let image_url = await gptImageCall((this.dataDoc.text as RichTextField)?.Text); + if (image_url) { + const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [image_url] }); + const source = Utils.prepend(result.accessPaths.agnostic.client); + const newDoc = Docs.Create.ImageDocument(source, { + x: NumCast(this.rootDoc.x) + NumCast(this.layoutDoc._width) + 10, + y: NumCast(this.rootDoc.y), + _height: 200, + _width: 200, + 'data-nativeWidth': result.nativeWidth, + 'data-nativeHeight': result.nativeHeight, + }); + if (DocListCast(Doc.MyOverlayDocs?.data).includes(this.rootDoc)) { + newDoc.overlayX = this.rootDoc.x; + newDoc.overlayY = NumCast(this.rootDoc.y) + NumCast(this.rootDoc._height); + Doc.AddDocToList(Doc.MyOverlayDocs, undefined, newDoc); + } else { + this.props.addDocument?.(newDoc); + } + // Create link between prompt and image + DocUtils.MakeLink(this.rootDoc, newDoc, { linkRelationship: 'Image Prompt' }); + } + } catch (err) { + console.log(err); + return ''; + } + }; + breakupDictation = () => { if (this._editorView && this._recording) { this.stopDictation(true); @@ -1129,6 +1195,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props); this.autoLink(); } + // Accessing editor and text doc for gpt assisted text edits + if (this._editorView && selected) { + AnchorMenu.Instance?.setEditorView(this._editorView); + AnchorMenu.Instance?.setTextDoc(this.dataDoc); + } }), { fireImmediately: true } ); @@ -1863,6 +1934,45 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps </div> ); } + @computed get overlayAlternateIcon() { + const usePath = this.rootDoc[`${this.props.fieldKey}-usePath`]; + return ( + <Tooltip + title={ + <div className="dash-tooltip"> + toggle between + <span style={{ color: usePath === undefined ? 'black' : undefined }}> + <em> primary, </em> + </span> + <span style={{ color: usePath === 'alternate' ? 'black' : undefined }}> + <em>alternate, </em> + </span> + and show + <span style={{ color: usePath === 'alternate:hover' ? 'black' : undefined }}> + <em> alternate on hover</em> + </span> + </div> + }> + <div + className="formattedTextBox-alternateButton" + onPointerDown={e => + setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => (this.rootDoc[`_${this.props.fieldKey}-usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined)) + } + style={{ + display: this.props.isContentActive() && !SnappingManager.GetIsDragging() ? 'flex' : 'none', + background: usePath === undefined ? 'white' : usePath === 'alternate' ? 'black' : 'gray', + color: usePath === undefined ? 'black' : 'white', + }}> + <FontAwesomeIcon icon="turn-up" size="sm" /> + </div> + </Tooltip> + ); + } + @computed get fieldKey() { + const usePath = StrCast(this.rootDoc[`${this.props.fieldKey}-usePath`]); + return this.props.fieldKey + (usePath && (!usePath.includes(':hover') || this._isHovering) ? `-${usePath.replace(':hover', '')}` : ''); + } + @observable _isHovering = false; render() { TraceMobx(); const active = this.props.isContentActive() || this.props.isSelected(); @@ -1879,7 +1989,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps const styleFromLayoutString = Doc.styleFromLayoutString(this.rootDoc, this.layoutDoc, this.props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._headerHeight}px' > return styleFromLayoutString?.height === '0px' ? null : ( <div - className="formattedTextBox-cont" + className="formattedTextBox" + onPointerEnter={action(() => (this._isHovering = true))} + onPointerLeave={action(() => (this._isHovering = false))} ref={r => r?.addEventListener( 'wheel', // if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this) @@ -1901,6 +2013,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps width: `${100 / scale}%`, height: `${100 / scale}%`, }), + display: !SnappingManager.GetIsDragging() && this.props.thumbShown?.() ? 'none' : undefined, transition: 'inherit', // overflowY: this.layoutDoc._autoHeight ? "hidden" : undefined, color: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color), @@ -1952,6 +2065,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps {this.noSidebar || this.props.dontSelectOnLoad || !this.SidebarShown || this.sidebarWidthPercent === '0%' ? null : this.sidebarCollection} {this.noSidebar || this.Document._noSidebar || this.props.dontSelectOnLoad || this.Document._singleLine ? null : this.sidebarHandle} {this.audioHandle} + {this.props.dontScale ? null : this.overlayAlternateIcon} </div> </div> ); diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index 68b0488a2..4dfe07b24 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -8,6 +8,7 @@ import { AclAugment, AclSelfEdit, Doc } from '../../../../fields/Doc'; import { GetEffectiveAcl } from '../../../../fields/util'; import { Utils } from '../../../../Utils'; import { Docs } from '../../../documents/Documents'; +import { RTFMarkup } from '../../../util/RTFMarkup'; import { SelectionManager } from '../../../util/SelectionManager'; import { OpenWhere } from '../DocumentView'; import { liftListItem, sinkListItem } from './prosemirrorPatches.js'; @@ -178,6 +179,83 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(1), state.doc.resolve(state.doc.content.size - 1)))); return true; }); + bind('Cmd-?', (state: EditorState, dispatch: (tx: Transaction) => void) => { + RTFMarkup.Instance.open(); + return true; + }); + bind('Cmd-e', (state: EditorState, dispatch: (tx: Transaction) => void) => { + if (!state.selection.empty) { + const mark = state.schema.marks.summarizeInclusive.create(); + const tr = state.tr.addMark(state.selection.$from.pos, state.selection.$to.pos, mark); + const content = tr.selection.content(); + tr.selection.replaceWith(tr, schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() })); + dispatch(tr); + } + return true; + }); + bind('Cmd-]', (state: EditorState, dispatch: (tx: Transaction) => void) => { + const resolved = state.doc.resolve(state.selection.from) as any; + const tr = state.tr; + if (resolved?.parent.type.name === 'paragraph') { + tr.setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'right' }, resolved.parent.marks); + } else { + const node = resolved.nodeAfter; + const sm = state.storedMarks || undefined; + if (node) { + tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'right' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]); + } + } + dispatch(tr); + return true; + }); + bind('Cmd-\\', (state: EditorState, dispatch: (tx: Transaction) => void) => { + const resolved = state.doc.resolve(state.selection.from) as any; + const tr = state.tr; + if (resolved?.parent.type.name === 'paragraph') { + tr.setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'center' }, resolved.parent.marks); + } else { + const node = resolved.nodeAfter; + const sm = state.storedMarks || undefined; + if (node) { + tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'center' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]); + } + } + dispatch(tr); + return true; + }); + bind('Cmd-[', (state: EditorState, dispatch: (tx: Transaction) => void) => { + const resolved = state.doc.resolve(state.selection.from) as any; + const tr = state.tr; + if (resolved?.parent.type.name === 'paragraph') { + tr.setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'left' }, resolved.parent.marks); + } else { + const node = resolved.nodeAfter; + const sm = state.storedMarks || undefined; + if (node) { + tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'left' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]); + } + } + dispatch(tr); + return true; + }); + + bind('Cmd-f', (state: EditorState, dispatch: (tx: Transaction) => void) => { + const content = state.tr.selection.empty ? undefined : state.tr.selection.content().content.textBetween(0, state.tr.selection.content().size + 1); + const newNode = schema.nodes.footnote.create({}, content ? state.schema.text(content) : undefined); + const tr = state.tr; + tr.replaceSelectionWith(newNode); // replace insertion with a footnote. + dispatch( + tr.setSelection( + new NodeSelection( // select the footnote node to open its display + tr.doc.resolve( + // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node) + tr.selection.anchor - (tr.selection.$anchor.nodeBefore?.nodeSize || 0) + ) + ) + ) + ); + return true; + }); bind('Ctrl-a', (state: EditorState, dispatch: (tx: Transaction) => void) => { dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(1), state.doc.resolve(state.doc.content.size - 1)))); diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index e691869cc..cc19d12bd 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -5,7 +5,7 @@ import { Id } from '../../../../fields/FieldSymbols'; import { ComputedField } from '../../../../fields/ScriptField'; import { NumCast, StrCast } from '../../../../fields/Types'; import { normalizeEmail } from '../../../../fields/util'; -import { returnFalse, Utils } from '../../../../Utils'; +import { Utils } from '../../../../Utils'; import { DocServer } from '../../../DocServer'; import { Docs, DocUtils } from '../../../documents/Documents'; import { FormattedTextBox } from './FormattedTextBox'; @@ -28,7 +28,7 @@ export class RichTextRules { emDash, // > blockquote - wrappingInputRule(/^\s*>\s$/, schema.nodes.blockquote), + wrappingInputRule(/%>$/, schema.nodes.blockquote), // 1. create numerical ordered list wrappingInputRule( @@ -190,21 +190,6 @@ export class RichTextRules { } }), - // %f create footnote - new InputRule(new RegExp(/%f$/), (state, match, start, end) => { - const newNode = schema.nodes.footnote.create({}); - const tr = state.tr; - tr.deleteRange(start, end).replaceSelectionWith(newNode); // replace insertion with a footnote. - return tr.setSelection( - new NodeSelection( // select the footnote node to open its display - tr.doc.resolve( - // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node) - tr.selection.anchor - (tr.selection.$anchor.nodeBefore?.nodeSize || 0) - ) - ) - ); - }), - // activate a style by name using prefix '%<color name>' new InputRule(new RegExp(/%[a-z]+$/), (state, match, start, end) => { const color = match[0].substring(1, match[0].length); @@ -229,6 +214,12 @@ export class RichTextRules { }), // stop using active style + new InputRule(new RegExp(/%alt$/), (state, match, start, end) => { + setTimeout(() => (this.Document[this.TextBox.props.fieldKey + '-usePath'] = this.Document[this.TextBox.props.fieldKey + '-usePath'] ? undefined : 'alternate')); + return state.tr.deleteRange(start, end); + }), + + // stop using active style new InputRule(new RegExp(/%%$/), (state, match, start, end) => { const tr = state.tr.deleteRange(start, end); const marks = state.tr.selection.$anchor.nodeBefore?.marks; @@ -250,22 +241,26 @@ export class RichTextRules { const fieldKey = match[1]; const docId = match[3]?.replace(':', ''); const value = match[2]?.substring(1); + const linkToDoc = (target: Doc) => { + const rstate = this.TextBox.EditorView?.state; + const selection = rstate?.selection.$from.pos; + if (rstate) { + this.TextBox.EditorView?.dispatch(rstate.tr.setSelection(new TextSelection(rstate.doc.resolve(start), rstate.doc.resolve(end - 3)))); + } + + DocUtils.MakeLink(this.TextBox.getAnchor(true), target, { linkRelationship: 'portal to:portal from' }); + + const fstate = this.TextBox.EditorView?.state; + if (fstate && selection) { + this.TextBox.EditorView?.dispatch(fstate.tr.setSelection(new TextSelection(fstate.doc.resolve(selection)))); + } + }; if (!fieldKey) { if (docId) { - DocServer.GetRefField(docId).then(docx => { - const rstate = this.TextBox.EditorView?.state; - const selection = rstate?.selection.$from.pos; - if (rstate) { - this.TextBox.EditorView?.dispatch(rstate.tr.setSelection(new TextSelection(rstate.doc.resolve(start), rstate.doc.resolve(end - 3)))); - } - const target = (docx instanceof Doc && docx) || Docs.Create.FreeformDocument([], { title: docId, _width: 500, _height: 500 }, docId); - DocUtils.MakeLink(this.TextBox.getAnchor(true), target, { linkRelationship: 'portal to:portal from' }); - - const fstate = this.TextBox.EditorView?.state; - if (fstate && selection) { - this.TextBox.EditorView?.dispatch(fstate.tr.setSelection(new TextSelection(fstate.doc.resolve(selection)))); - } - }); + const target = DocServer.QUERY_SERVER_CACHE(docId); + if (target) setTimeout(() => linkToDoc(target)); + else DocServer.GetRefField(docId).then(docx => linkToDoc((docx instanceof Doc && docx) || Docs.Create.FreeformDocument([], { title: docId + '(auto)', _width: 500, _height: 500 }, docId))); + return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 3); } return state.tr; @@ -296,7 +291,7 @@ export class RichTextRules { // create an inline equation node // eq:<equation>> - new InputRule(new RegExp(/:eq([a-zA-Z-0-9\(\)]*)$/), (state, match, start, end) => { + new InputRule(new RegExp(/%eq([a-zA-Z-0-9\(\)]*)$/), (state, match, start, end) => { const fieldKey = 'math' + Utils.GenerateGuid(); this.TextBox.dataDoc[fieldKey] = match[1]; const tr = state.tr.setSelection(new TextSelection(state.tr.doc.resolve(end - 3), state.tr.doc.resolve(end))).replaceSelectionWith(schema.nodes.equation.create({ fieldKey })); @@ -374,7 +369,7 @@ export class RichTextRules { const content = selected.selection.content(); const replaced = node ? selected.replaceRangeWith(start, end, schema.nodes.summary.create({ visibility: true, text: content, textslice: content.toJSON() })) : state.tr; - return replaced.setSelection(new TextSelection(replaced.doc.resolve(end + 1))).setStoredMarks([...node.marks, ...sm]); + return replaced.setSelection(new TextSelection(replaced.doc.resolve(end))).setStoredMarks([...node.marks, ...sm]); }), new InputRule(new RegExp(/%\)/), (state, match, start, end) => { diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts index 3898490d3..5b47e8a70 100644 --- a/src/client/views/nodes/formattedText/marks_rts.ts +++ b/src/client/views/nodes/formattedText/marks_rts.ts @@ -349,7 +349,7 @@ export const marks: { [index: string]: MarkSpec } = { group: 'inline', toDOM(node: any) { const uid = node.attrs.userid.replace('.', '').replace('@', ''); - const min = Math.round(node.attrs.modified / 12); + const min = Math.round(node.attrs.modified / 60); const hr = Math.round(min / 60); const day = Math.round(hr / 60 / 24); const remote = node.attrs.userid !== Doc.CurrentUserEmail ? ' UM-remote' : ''; diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 807a19771..bd2be8f11 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -49,7 +49,6 @@ export interface pinDataTypes { clippable?: boolean; datarange?: boolean; dataview?: boolean; - textview?: boolean; poslayoutview?: boolean; dataannos?: boolean; } @@ -382,14 +381,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const temporal = [DocumentType.AUDIO, DocumentType.VID].includes(targetType); const clippable = [DocumentType.COMPARISON].includes(targetType); const datarange = [DocumentType.FUNCPLOT].includes(targetType); - const dataview = [DocumentType.INK, DocumentType.COL, DocumentType.IMG].includes(targetType) && target?.activeFrame === undefined; + const dataview = [DocumentType.INK, DocumentType.COL, DocumentType.IMG, DocumentType.RTF].includes(targetType) && target?.activeFrame === undefined; const poslayoutview = [DocumentType.COL].includes(targetType) && target?.activeFrame === undefined; - const textview = [DocumentType.RTF].includes(targetType) && target?.activeFrame === undefined; const viewType = targetType === DocumentType.COL; const filters = true; const pivot = true; const dataannos = false; - return { scrollable, pannable, inkable, viewType, pivot, filters, temporal, clippable, dataview, datarange, textview, poslayoutview, dataannos }; + return { scrollable, pannable, inkable, viewType, pivot, filters, temporal, clippable, dataview, datarange, poslayoutview, dataannos }; } @action @@ -527,12 +525,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { bestTarget._dataTransition = `all ${transTime}ms`; const fkey = Doc.LayoutFieldKey(bestTarget); Doc.GetProto(bestTarget)[fkey] = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData; - bestTarget[fkey + '-useAlt'] = activeItem.presUseAlt; + bestTarget[fkey + '-usePath'] = activeItem.presUsePath; setTimeout(() => (bestTarget._dataTransition = undefined), transTime + 10); } - if ((pinDataTypes?.textview && activeItem.presData !== undefined) || (!pinDataTypes && activeItem.presData !== undefined)) { - Doc.GetProto(bestTarget)[Doc.LayoutFieldKey(bestTarget)] = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData; - } if (pinDataTypes?.poslayoutview || (!pinDataTypes && activeItem.presPinLayoutData !== undefined)) { changed = true; const layoutField = Doc.LayoutFieldKey(bestTarget); @@ -567,6 +562,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { bestTarget._panY = viewport.panY; const dv = DocumentManager.Instance.getDocumentView(bestTarget); if (dv) { + changed = true; const computedScale = NumCast(activeItem.presZoom, 1) * Math.min(dv.props.PanelWidth() / viewport.width, dv.props.PanelHeight() / viewport.height); activeItem.presMovement === PresMovement.Zoom && (bestTarget._viewScale = computedScale); dv.ComponentView?.brushView?.(viewport); @@ -608,19 +604,17 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { pinProps.pinData.clippable || pinProps.pinData.datarange || pinProps.pinData.dataview || - pinProps.pinData.textview || pinProps.pinData.poslayoutview || pinProps?.activeFrame !== undefined; const fkey = Doc.LayoutFieldKey(targetDoc); if (pinProps.pinData.dataview) { - pinDoc.presUseAlt = targetDoc[fkey + '-useAlt']; + pinDoc.presUsePath = targetDoc[fkey + '-usePath']; pinDoc.presData = targetDoc[fkey] instanceof ObjectField ? (targetDoc[fkey] as ObjectField)[Copy]() : targetDoc.data; } if (pinProps.pinData.dataannos) { const fkey = Doc.LayoutFieldKey(targetDoc); pinDoc.presAnnotations = new List<Doc>(DocListCast(Doc.GetProto(targetDoc)[fkey + '-annotations']).filter(doc => !doc.unrendered)); } - if (pinProps.pinData.textview) pinDoc.presData = targetDoc[Doc.LayoutFieldKey(targetDoc)] instanceof ObjectField ? (targetDoc[Doc.LayoutFieldKey(targetDoc)] as ObjectField)[Copy]() : targetDoc.text; if (pinProps.pinData.inkable) { pinDoc.presFillColor = targetDoc.fillColor; pinDoc.presColor = targetDoc.color; diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index 502eef373..62e5314c3 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -353,14 +353,11 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { @undoBatch @action showRecording = (activeItem: Doc, iconClick: boolean = false) => { - // if (iconClick) PresElementBox.showVideo = true; - // if (!PresElementBox.showVideo) return; - // remove the overlays on switch *IF* not opened from the specific icon if (!iconClick) PresElementBox.removeEveryExistingRecordingInOverlay(); if (activeItem.recording) { - Doc.AddDocToList(Doc.MyOverlayDocs, undefined, Cast(activeItem.recording, Doc, null)); + Doc.AddDocToList(Doc.MyOverlayDocs, undefined, DocCast(activeItem.recording)); } }; @@ -437,15 +434,13 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { </div> </Tooltip> ); - if (!Doc.noviceMode) { - items.push( - <Tooltip key="slash" title={<div className="dash-tooltip">{this.recordingIsInOverlay ? 'Hide Recording' : `${PresElementBox.videoIsRecorded(activeItem) ? 'Show' : 'Start'} recording`}</div>}> - <div className="slideButton" onClick={e => (this.recordingIsInOverlay ? this.hideRecording(e, true) : this.startRecording(e, activeItem))} style={{ fontWeight: 700 }}> - <FontAwesomeIcon icon={`video${this.recordingIsInOverlay ? '-slash' : ''}`} onPointerDown={e => e.stopPropagation()} /> - </div> - </Tooltip> - ); - } + items.push( + <Tooltip key="slash" title={<div className="dash-tooltip">{this.recordingIsInOverlay ? 'Hide Recording' : `${PresElementBox.videoIsRecorded(activeItem) ? 'Show' : 'Start'} recording`}</div>}> + <div className="slideButton" onClick={e => (this.recordingIsInOverlay ? this.hideRecording(e, true) : this.startRecording(e, activeItem))} style={{ fontWeight: 700 }}> + <FontAwesomeIcon icon={`video${this.recordingIsInOverlay ? '-slash' : ''}`} onPointerDown={e => e.stopPropagation()} /> + </div> + </Tooltip> + ); if (this.indexInPres !== 0) { items.push( <Tooltip key="arrow" title={<div className="dash-tooltip">{activeItem.groupWithUp ? 'Ungroup' : 'Group with up'}</div>}> diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index 7392d2706..d6dddf71a 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -10,8 +10,11 @@ import { SelectionManager } from '../../util/SelectionManager'; import { AntimodeMenu, AntimodeMenuProps } from '../AntimodeMenu'; import { LinkPopup } from '../linking/LinkPopup'; import { ButtonDropdown } from '../nodes/formattedText/RichTextMenu'; -import './AnchorMenu.scss'; +import { gptAPICall, GPTCallType } from '../../apis/gpt/GPT'; +import { GPTPopup, GPTPopupMode } from './GPTPopup/GPTPopup'; import { LightboxView } from '../LightboxView'; +import { EditorView } from 'prosemirror-view'; +import './AnchorMenu.scss'; @observer export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { @@ -43,6 +46,56 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { @observable public Status: 'marquee' | 'annotation' | '' = ''; + // GPT additions + @observable private GPTpopupText: string = ''; + @observable private loadingGPT: boolean = false; + @observable private showGPTPopup: boolean = false; + @observable private GPTMode: GPTPopupMode = GPTPopupMode.SUMMARY; + @observable private selectedText: string = ''; + @observable private editorView?: EditorView; + @observable private textDoc?: Doc; + @observable private highlightRange: number[] | undefined; + private selectionRange: number[] | undefined; + + @action + setGPTPopupVis = (vis: boolean) => { + this.showGPTPopup = vis; + }; + @action + setGPTMode = (mode: GPTPopupMode) => { + this.GPTMode = mode; + }; + + @action + setGPTPopupText = (txt: string) => { + this.GPTpopupText = txt; + }; + + @action + setLoading = (loading: boolean) => { + this.loadingGPT = loading; + }; + + @action + setHighlightRange(r: number[] | undefined) { + this.highlightRange = r; + } + + @action + public setSelectedText = (txt: string) => { + this.selectedText = txt; + }; + + @action + public setEditorView = (editor: EditorView) => { + this.editorView = editor; + }; + + @action + public setTextDoc = (textDoc: Doc) => { + this.textDoc = textDoc; + }; + public onMakeAnchor: () => Opt<Doc> = () => undefined; // Method to get anchor from text search public OnCrop: (e: PointerEvent) => void = unimplementedFunction; @@ -76,18 +129,94 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { componentDidMount() { this._disposer2 = reaction( () => this._opacity, - opacity => !opacity && (this._showLinkPopup = false), + opacity => { + if (!opacity) { + this._showLinkPopup = false; + this.setGPTPopupVis(false); + this.setGPTPopupText(''); + } + }, { fireImmediately: true } ); this._disposer = reaction( () => SelectionManager.Views().slice(), selected => { this._showLinkPopup = false; + this.setGPTPopupVis(false); + this.setGPTPopupText(''); AnchorMenu.Instance.fadeOut(true); } ); } + /** + * Invokes the API with the selected text and stores it in the summarized text. + * @param e pointer down event + */ + gptSummarize = async (e: React.PointerEvent) => { + this.setHighlightRange(undefined); + this.setGPTPopupVis(true); + this.setGPTMode(GPTPopupMode.SUMMARY); + this.setLoading(true); + + try { + const res = await gptAPICall(this.selectedText, GPTCallType.SUMMARY); + if (res) { + this.setGPTPopupText(res); + } else { + this.setGPTPopupText('Something went wrong.'); + } + } catch (err) { + console.error(err); + } + + this.setLoading(false); + }; + + /** + * Makes a GPT call to edit selected text. + * @returns nothing + */ + gptEdit = async () => { + if (!this.editorView) return; + this.setHighlightRange(undefined); + const state = this.editorView.state; + const sel = state.selection; + const fullText = state.doc.textBetween(0, this.editorView.state.doc.content.size, ' \n'); + const selectedText = state.doc.textBetween(sel.from, sel.to); + + this.setGPTPopupVis(true); + this.setGPTMode(GPTPopupMode.EDIT); + this.setLoading(true); + + try { + let res = await gptAPICall(selectedText, GPTCallType.EDIT); + // let res = await this.mockGPTCall(); + if (!res) return; + res = res.trim(); + const resultText = fullText.slice(0, sel.from - 1) + res + fullText.slice(sel.to - 1); + + if (res) { + this.setGPTPopupText(resultText); + this.setHighlightRange([sel.from - 1, sel.from - 1 + res.length]); + } else { + this.setGPTPopupText('Something went wrong.'); + } + } catch (err) { + console.error(err); + } + + this.setLoading(false); + }; + + /** + * Replaces text suggestions from GPT. + */ + replaceText = (replacement: string) => { + if (!this.editorView || !this.textDoc) return; + this.textDoc.text = replacement; + }; + pointerDown = (e: React.PointerEvent) => { setupMoveUpEvents( this, @@ -180,6 +309,31 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { this.highlightColor = Utils.colorString(col); }; + /** + * Returns whether the selected text can be summarized. The goal is to have + * all selected text available to summarize but its only supported for pdf and web ATM. + * @returns Whether the GPT icon for summarization should appear + */ + canSummarize = (): boolean => { + const docs = SelectionManager.Docs(); + if (docs.length > 0) { + return docs.some(doc => doc.type === 'pdf' || doc.type === 'web'); + } + return false; + }; + + /** + * Returns whether the selected text can be edited. + * @returns Whether the GPT icon for summarization should appear + */ + canEdit = (): boolean => { + const docs = SelectionManager.Docs(); + if (docs.length > 0) { + return docs.some(doc => doc.type === 'rtf'); + } + return false; + }; + render() { const buttons = this.Status === 'marquee' ? ( @@ -190,6 +344,25 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { <FontAwesomeIcon icon="comment-alt" size="lg" /> </button> </Tooltip> + {/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection*/} + {AnchorMenu.Instance.StartCropDrag === unimplementedFunction && this.canSummarize() && ( + <Tooltip key="gpt" title={<div className="dash-tooltip">Summarize with AI</div>}> + <button className="antimodeMenu-button annotate" onPointerDown={this.gptSummarize} style={{ cursor: 'grab' }}> + <FontAwesomeIcon icon="comment-dots" size="lg" /> + </button> + </Tooltip> + )} + <GPTPopup + key="gptpopup" + visible={this.showGPTPopup} + text={this.GPTpopupText} + highlightRange={this.highlightRange} + loading={this.loadingGPT} + callSummaryApi={this.gptSummarize} + callEditApi={this.gptEdit} + replaceText={this.replaceText} + mode={this.GPTMode} + /> {AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : ( <Tooltip key="annoaudiotate" title={<div className="dash-tooltip">Click to Record Annotation</div>}> <button className="antimodeMenu-button annotate" onPointerDown={this.audioDown} style={{ cursor: 'grab' }}> @@ -197,6 +370,13 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { </button> </Tooltip> )} + {this.canEdit() && ( + <Tooltip key="gpttextedit" title={<div className="dash-tooltip">AI edit suggestions</div>}> + <button className="antimodeMenu-button annotate" onPointerDown={this.gptEdit} style={{ cursor: 'grab' }}> + <FontAwesomeIcon icon="pencil-alt" size="lg" /> + </button> + </Tooltip> + )} <Tooltip key="link" title={<div className="dash-tooltip">Find document to link to selected text</div>}> <button className="antimodeMenu-button link" onPointerDown={this.toggleLinkPopup}> <FontAwesomeIcon style={{ position: 'absolute', transform: 'scale(1.5)' }} icon={'search'} size="lg" /> diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss new file mode 100644 index 000000000..44413ede7 --- /dev/null +++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss @@ -0,0 +1,132 @@ +$textgrey: #707070; +$lighttextgrey: #a3a3a3; +$greyborder: #d3d3d3; +$lightgrey: #ececec; +$button: #5b97ff; +$highlightedText: #82e0ff; + +.summary-box { + display: flex; + flex-direction: column; + justify-content: space-between; + background-color: #ffffff; + box-shadow: 0 2px 5px #7474748d; + color: $textgrey; + position: fixed; + bottom: 10px; + right: 10px; + width: 250px; + min-height: 200px; + border-radius: 15px; + padding: 15px; + padding-bottom: 0; + z-index: 999; + + .summary-heading { + display: flex; + align-items: center; + border-bottom: 1px solid $greyborder; + padding-bottom: 5px; + + .summary-text { + font-size: 12px; + font-weight: 500; + } + } + + label { + color: $textgrey; + font-size: 12px; + font-weight: 400; + letter-spacing: 1px; + margin: 0; + padding-right: 5px; + } + + a { + cursor: pointer; + } + + .content-wrapper { + padding-top: 10px; + min-height: 50px; + max-height: 150px; + overflow-y: auto; + } + + .btns-wrapper { + height: 50px; + display: flex; + justify-content: space-between; + align-items: center; + + .summarizing { + display: flex; + align-items: center; + } + } + + button { + font-size: 9px; + padding: 10px; + color: #ffffff; + background-color: $button; + border-radius: 5px; + } + + .text-btn { + &:hover { + background-color: $button; + } + } + + .btn-secondary { + font-size: 8px; + padding: 10px 5px; + background-color: $lightgrey; + color: $textgrey; + &:hover { + background-color: $lightgrey; + } + } + + .icon-btn { + background-color: #ffffff; + padding: 10px; + border-radius: 50%; + color: $button; + border: 1px solid $button; + } + + .ai-warning { + padding: 10px 0; + font-size: 10px; + color: $lighttextgrey; + border-top: 1px solid $greyborder; + } + + .highlighted-text { + background-color: $highlightedText; + } +} + +// Typist CSS +.Typist .Cursor { + display: inline-block; +} +.Typist .Cursor--blinking { + opacity: 1; + animation: blink 1s linear infinite; +} + +@keyframes blink { + 0% { + opacity: 1; + } + 50% { + opacity: 0; + } + 100% { + opacity: 1; + } +} diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx new file mode 100644 index 000000000..cc0072a94 --- /dev/null +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -0,0 +1,188 @@ +import React = require('react'); +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import ReactLoading from 'react-loading'; +import Typist from 'react-typist'; +import { Doc } from '../../../../fields/Doc'; +import { Docs } from '../../../documents/Documents'; +import './GPTPopup.scss'; + +export enum GPTPopupMode { + SUMMARY, + EDIT, +} + +interface GPTPopupProps { + visible: boolean; + text: string; + loading: boolean; + mode: GPTPopupMode; + callSummaryApi: (e: React.PointerEvent) => Promise<void>; + callEditApi: (e: React.PointerEvent) => Promise<void>; + replaceText: (replacement: string) => void; + highlightRange?: number[]; +} + +@observer +export class GPTPopup extends React.Component<GPTPopupProps> { + static Instance: GPTPopup; + + @observable + private done: boolean = false; + @observable + private sidebarId: string = ''; + + @action + public setDone = (done: boolean) => { + this.done = done; + }; + @action + public setSidebarId = (id: string) => { + this.sidebarId = id; + }; + + public addDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean = () => false; + + /** + * Transfers the summarization text to a sidebar annotation text document. + */ + private transferToText = () => { + const newDoc = Docs.Create.TextDocument(this.props.text.trim(), { + _width: 200, + _height: 50, + _fitWidth: true, + _autoHeight: true, + }); + this.addDoc(newDoc, this.sidebarId); + }; + + constructor(props: GPTPopupProps) { + super(props); + GPTPopup.Instance = this; + } + + componentDidUpdate = () => { + if (this.props.loading) { + this.setDone(false); + } + }; + + summaryBox = () => ( + <> + <div> + {this.heading('SUMMARY')} + <div className="content-wrapper"> + {!this.props.loading && + (!this.done ? ( + <Typist + key={this.props.text} + avgTypingDelay={15} + cursor={{ hideWhenDone: true }} + onTypingDone={() => { + setTimeout(() => { + this.setDone(true); + }, 500); + }}> + {this.props.text} + </Typist> + ) : ( + this.props.text + ))} + </div> + </div> + {!this.props.loading && ( + <div className="btns-wrapper"> + {this.done ? ( + <> + <button className="icon-btn" onPointerDown={e => this.props.callSummaryApi(e)}> + <FontAwesomeIcon icon="redo-alt" size="lg" /> + </button> + <button + className="text-btn" + onClick={e => { + this.transferToText(); + }}> + Transfer to Text + </button> + </> + ) : ( + <div className="summarizing"> + <span>Summarizing</span> + <ReactLoading type="bubbles" color="#bcbcbc" width={20} height={20} /> + <button + className="btn-secondary" + onClick={e => { + this.setDone(true); + }}> + Stop Animation + </button> + </div> + )} + </div> + )} + </> + ); + + editBox = () => { + const hr = this.props.highlightRange; + return ( + <> + <div> + {this.heading('TEXT EDIT SUGGESTIONS')} + <div className="content-wrapper"> + {hr && ( + <div> + {this.props.text.slice(0, hr[0])} <span className="highlighted-text">{this.props.text.slice(hr[0], hr[1])}</span> {this.props.text.slice(hr[1])} + </div> + )} + </div> + </div> + {hr && !this.props.loading && ( + <> + <div className="btns-wrapper"> + <> + <button className="icon-btn" onPointerDown={e => this.props.callEditApi(e)}> + <FontAwesomeIcon icon="redo-alt" size="lg" /> + </button> + <button + className="text-btn" + onClick={e => { + this.props.replaceText(this.props.text); + }}> + Replace Text + </button> + </> + </div> + {this.aiWarning()} + </> + )} + </> + ); + }; + + aiWarning = () => + this.done ? ( + <div className="ai-warning"> + <FontAwesomeIcon icon="exclamation-circle" size="sm" style={{ paddingRight: '5px' }} /> + AI generated responses can contain inaccurate or misleading content. + </div> + ) : ( + <></> + ); + + heading = (headingText: string) => ( + <div className="summary-heading"> + <label className="summary-text">{headingText}</label> + {this.props.loading && <ReactLoading type="spin" color="#bcbcbc" width={14} height={14} />} + </div> + ); + + render() { + return ( + <div className="summary-box" style={{ display: this.props.visible ? 'flex' : 'none' }}> + {this.props.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.editBox()} + </div> + ); + } +} diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index d82a7d1ae..fce67e7fc 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -24,6 +24,7 @@ import { AnchorMenu } from './AnchorMenu'; import { Annotation } from './Annotation'; import './PDFViewer.scss'; import React = require('react'); +import { GPTPopup } from './GPTPopup/GPTPopup'; const PDFJSViewer = require('pdfjs-dist/web/pdf_viewer'); const pdfjsLib = require('pdfjs-dist'); const _global = (window /* browser */ || global) /* node */ as any; @@ -40,9 +41,10 @@ interface IViewerProps extends FieldViewProps { fieldKey: string; pdf: Pdfjs.PDFDocumentProxy; url: string; + sidebarAddDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean; loaded?: (nw: number, nh: number, np: number) => void; setPdfViewer: (view: PDFViewer) => void; - anchorMenuClick?: () => undefined | ((anchor: Doc) => void); + anchorMenuClick?: () => undefined | ((anchor: Doc, summarize?: boolean) => void); crop: (region: Doc | undefined, addCrop?: boolean) => Doc | undefined; } @@ -88,7 +90,7 @@ export class PDFViewer extends React.Component<IViewerProps> { @observable isAnnotating = false; // key where data is stored @computed get allAnnotations() { - return DocUtils.FilterDocs(DocListCast(this.props.dataDoc[this.props.fieldKey + '-annotations']), this.props.docFilters(), this.props.docRangeFilters(), undefined); + return DocUtils.FilterDocs(DocListCast(this.props.dataDoc[this.props.fieldKey + '-annotations']), this.props.docFilters(), this.props.docRangeFilters()); } @computed get inlineTextAnnotations() { return this.allAnnotations.filter(a => a.textInlineAnnotations); @@ -409,10 +411,18 @@ export class PDFViewer extends React.Component<IViewerProps> { document.removeEventListener('pointerup', this.onSelectEnd); const sel = window.getSelection(); + if (sel) { + AnchorMenu.Instance.setSelectedText(sel.toString()); + } + if (sel?.type === 'Range') { this.createTextAnnotation(sel, sel.getRangeAt(0)); AnchorMenu.Instance.jumpTo(e.clientX, e.clientY); } + + // Changing which document to add the annotation to (the currently selected PDF) + GPTPopup.Instance.setSidebarId('data-sidebar'); + GPTPopup.Instance.addDoc = this.props.sidebarAddDoc; }; @action @@ -422,7 +432,7 @@ export class PDFViewer extends React.Component<IViewerProps> { const clientRects = selRange.getClientRects(); for (let i = 0; i < clientRects.length; i++) { const rect = clientRects.item(i); - if (rect && rect.width !== this._mainCont.current.clientWidth && rect.width) { + if (rect?.width && rect.width < this._mainCont.current.clientWidth / this.props.ScreenToLocalTransform().Scale) { const scaleX = this._mainCont.current.offsetWidth / boundingRect.width; const pdfScale = NumCast(this.props.layoutDoc._viewScale, 1); const annoBox = document.createElement('div'); @@ -526,7 +536,8 @@ export class PDFViewer extends React.Component<IViewerProps> { NativeWidth={returnZero} NativeHeight={returnZero} setContentView={emptyFunction} // override setContentView to do nothing - pointerEvents={SnappingManager.GetIsDragging() ? returnAll : returnNone} + pointerEvents={SnappingManager.GetIsDragging() ? returnAll : returnNone} // freeform view doesn't get events unless something is being dragged onto it. + childPointerEvents={'all'} // but freeform children need to get events to allow text editing, etc renderDepth={this.props.renderDepth + 1} isAnnotationOverlay={true} fieldKey={this.props.fieldKey + '-annotations'} diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 8f93f1150..32b661bb6 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -211,7 +211,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { } @action static staticSearchCollection(rootDoc: Opt<Doc>, query: string) { - const blockedTypes = [DocumentType.PRESELEMENT, DocumentType.KVP, DocumentType.FILTER, DocumentType.SEARCH, DocumentType.SEARCHITEM, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING]; + const blockedTypes = [DocumentType.PRESELEMENT, DocumentType.MARKER, DocumentType.KVP, DocumentType.FILTER, DocumentType.SEARCH, DocumentType.SEARCHITEM, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING]; const blockedKeys = [ 'x', 'y', diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx index d63c25dbe..9bd2ba5ce 100644 --- a/src/client/views/topbar/TopBar.tsx +++ b/src/client/views/topbar/TopBar.tsx @@ -1,21 +1,22 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Button, FontSize, IconButton, Size } from 'browndash-components'; +import { Button, IconButton, Size } from 'browndash-components'; import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { FaBug, FaCamera } from 'react-icons/fa'; -import { AclAdmin, Doc, DocListCast } from '../../../fields/Doc'; +import { FaBug, FaCamera, FaStamp } from 'react-icons/fa'; +import { AclAdmin, Doc } from '../../../fields/Doc'; import { StrCast } from '../../../fields/Types'; import { GetEffectiveAcl } from '../../../fields/util'; import { DocumentManager } from '../../util/DocumentManager'; import { ReportManager } from '../../util/ReportManager'; +import { ServerStats } from '../../util/ServerStats'; import { SettingsManager } from '../../util/SettingsManager'; import { SharingManager } from '../../util/SharingManager'; import { UndoManager } from '../../util/UndoManager'; import { CollectionDockingView } from '../collections/CollectionDockingView'; import { ContextMenu } from '../ContextMenu'; import { DashboardView } from '../DashboardView'; -import { Borders, Colors } from '../global/globalEnums'; +import { Colors } from '../global/globalEnums'; import { MainView } from '../MainView'; import './TopBar.scss'; @@ -132,9 +133,10 @@ export class TopBar extends React.Component { @computed get topbarRight() { return ( <div className="topbar-right"> - <IconButton size={Size.SMALL} color={Colors.LIGHT_GRAY} onClick={() => ReportManager.Instance.open()} icon={<FaBug />} /> + <IconButton size={Size.SMALL} color={Colors.LIGHT_GRAY} onClick={ServerStats.Instance.open} icon={<FaStamp />} /> + <IconButton size={Size.SMALL} color={Colors.LIGHT_GRAY} onClick={ReportManager.Instance.open} icon={<FaBug />} /> <IconButton size={Size.SMALL} color={Colors.LIGHT_GRAY} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/', '_blank')} icon={<FontAwesomeIcon icon="question-circle" />} /> - <IconButton size={Size.SMALL} color={Colors.LIGHT_GRAY} onClick={() => SettingsManager.Instance.open()} icon={<FontAwesomeIcon icon="cog" />} /> + <IconButton size={Size.SMALL} color={Colors.LIGHT_GRAY} onClick={SettingsManager.Instance.open} icon={<FontAwesomeIcon icon="cog" />} /> {/* <Button text={'Logout'} borderRadius={5} hoverStyle={'gray'} backgroundColor={Colors.DARK_GRAY} color={this.textColor} fontSize={FontSize.SECONDARY} onClick={() => window.location.assign(Utils.prepend('/logout'))} /> */} </div> ); diff --git a/src/fields/DateField.ts b/src/fields/DateField.ts index 26f51b2d3..2ea619bd9 100644 --- a/src/fields/DateField.ts +++ b/src/fields/DateField.ts @@ -1,11 +1,11 @@ -import { Deserializable } from "../client/util/SerializationHelper"; -import { serializable, date } from "serializr"; -import { ObjectField } from "./ObjectField"; -import { Copy, ToScriptString, ToString } from "./FieldSymbols"; -import { scriptingGlobal, ScriptingGlobals } from "../client/util/ScriptingGlobals"; +import { Deserializable } from '../client/util/SerializationHelper'; +import { serializable, date } from 'serializr'; +import { ObjectField } from './ObjectField'; +import { Copy, ToScriptString, ToString } from './FieldSymbols'; +import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals'; @scriptingGlobal -@Deserializable("date") +@Deserializable('date') export class DateField extends ObjectField { @serializable(date()) readonly date: Date; @@ -24,7 +24,7 @@ export class DateField extends ObjectField { } [ToScriptString]() { - return `new DateField(new Date(${this.date.toISOString()}))`; + return `new DateField(new Date("${this.date.toISOString()}"))`; } [ToString]() { return this.date.toLocaleString(); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 6c808c145..a9be24c8b 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -29,9 +29,13 @@ import JSZip = require('jszip'); import * as JSZipUtils from '../JSZipUtils'; export namespace Field { export function toKeyValueString(doc: Doc, key: string): string { - const onDelegate = Object.keys(doc).includes(key); + const onDelegate = Object.keys(doc).includes(key.replace(/^_/, '')); const field = ComputedField.WithoutComputed(() => FieldValue(doc[key])); - return !Field.IsField(field) ? '' : (onDelegate ? '=' : '') + (field instanceof ComputedField ? `:=${field.script.originalScript}` : field instanceof ScriptField ? `$=${field.script.originalScript}` : Field.toScriptString(field)); + return !Field.IsField(field) + ? key.startsWith('_') + ? '=' + : '' + : (onDelegate ? '=' : '') + (field instanceof ComputedField ? `:=${field.script.originalScript}` : field instanceof ScriptField ? `$=${field.script.originalScript}` : Field.toScriptString(field)); } export function toScriptString(field: Field): string { switch (typeof field) { @@ -700,12 +704,12 @@ export namespace Doc { // this lists out all the tag ids that can be in a RichTextField that might contain document ids. // if a document is cloned, we need to make sure to clone all of these referenced documents as well; export const DocsInTextFieldIds = ['audioId', 'textId', 'anchorId', 'docId']; - export async function makeClone(doc: Doc, cloneMap: Map<string, Doc>, linkMap: Map<string, Doc>, rtfs: { copy: Doc; key: string; field: RichTextField }[], exclusions: string[], cloneLinks: boolean): Promise<Doc> { + export async function makeClone(doc: Doc, cloneMap: Map<string, Doc>, linkMap: Map<string, Doc>, rtfs: { copy: Doc; key: string; field: RichTextField }[], exclusions: string[], pruneDocs: Doc[], cloneLinks: boolean): Promise<Doc> { if (Doc.IsBaseProto(doc)) return doc; if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!; const copy = new Doc(undefined, true); cloneMap.set(doc[Id], copy); - const filter = [...exclusions, ...Cast(doc.cloneFieldFilter, listSpec('string'), [])]; + const filter = [...exclusions, ...StrListCast(doc.cloneFieldFilter)]; await Promise.all( Object.keys(doc).map(async key => { if (filter.includes(key)) return; @@ -716,7 +720,7 @@ export namespace Doc { const list = await Cast(doc[key], listSpec(Doc)); const docs = list && (await DocListCastAsync(list))?.filter(d => d instanceof Doc); if (docs !== undefined && docs.length) { - const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, cloneLinks))); + const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks))); assignKey(new List<Doc>(clones)); } else { assignKey(ObjectField.MakeCopy(field)); @@ -725,15 +729,13 @@ export namespace Doc { const docidsearch = new RegExp('(' + DocsInTextFieldIds.map(exp => '(' + exp + ')').join('|') + ')":"([a-z-A-Z0-9_]*)"', 'g'); const rawdocids = field.Data.match(docidsearch); const docids = rawdocids?.map((str: string) => - DocsInTextFieldIds.reduce((output, exp) => { - return output.replace(new RegExp(`${exp}":`, 'g'), ''); - }, str) + DocsInTextFieldIds.reduce((output, exp) => output.replace(new RegExp(`${exp}":`, 'g'), ''), str) .replace(/"/g, '') .trim() ); const results = docids && (await DocServer.GetRefFields(docids)); const docs = results && Array.from(Object.keys(results)).map(key => DocCast(results[key])); - docs && docs.map(doc => Doc.makeClone(doc, cloneMap, linkMap, rtfs, exclusions, cloneLinks)); + docs?.map(doc => doc && Doc.makeClone(doc, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks)); rtfs.push({ copy, key, field }); } } @@ -741,16 +743,17 @@ export namespace Doc { }; const docAtKey = doc[key]; if (docAtKey instanceof Doc) { - if (!Doc.IsSystem(docAtKey) && (key.startsWith('layout') || key === 'annotationOn' || key === 'proto' || ((key === 'anchor1' || key === 'anchor2') && doc.author === Doc.CurrentUserEmail))) { - assignKey(await Doc.makeClone(docAtKey, cloneMap, linkMap, rtfs, exclusions, cloneLinks)); + if (pruneDocs.includes(docAtKey)) { + // prune doc and do nothing + } else if (!Doc.IsSystem(docAtKey) && (key.startsWith('layout') || ['context', 'annotationOn', 'proto'].includes(key) || ((key === 'anchor1' || key === 'anchor2') && doc.author === Doc.CurrentUserEmail))) { + assignKey(await Doc.makeClone(docAtKey, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks)); } else { assignKey(docAtKey); } } else if (field instanceof RefField) { assignKey(field); } else if (cfield instanceof ComputedField) { - assignKey(cfield[Copy]()); - // ComputedField.MakeFunction(cfield.script.originalScript)); + assignKey(cfield[Copy]()); // ComputedField.MakeFunction(cfield.script.originalScript)); } else if (field instanceof ObjectField) { await copyObjectField(field); } else if (field instanceof Promise) { @@ -765,7 +768,7 @@ export namespace Doc { cloneLinks || ((cloneMap.has(DocCast(link.anchor1)?.[Id]) || cloneMap.has(DocCast(DocCast(link.anchor1)?.annotationOn)?.[Id])) && (cloneMap.has(DocCast(link.anchor2)?.[Id]) || cloneMap.has(DocCast(DocCast(link.anchor2)?.annotationOn)?.[Id]))) ) { - linkMap.set(link[Id], await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, cloneLinks)); + linkMap.set(link[Id], await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks)); } }); Doc.SetInPlace(copy, 'title', 'CLONE: ' + doc.title, true); @@ -801,11 +804,9 @@ export namespace Doc { export async function MakeClone(doc: Doc, cloneLinks = true, cloneMap: Map<string, Doc> = new Map()) { const linkMap = new Map<string, Doc>(); const rtfMap: { copy: Doc; key: string; field: RichTextField }[] = []; - const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf'], cloneLinks); + const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf'], doc.context ? [DocCast(doc.context)] : [], cloneLinks); const repaired = new Set<Doc>(); const linkedDocs = Array.from(linkMap.values()); - const clonedDocs = [...Array.from(cloneMap.values()), ...linkedDocs]; - clonedDocs.map(clone => Doc.repairClone(clone, cloneMap, repaired)); linkedDocs.map((link: Doc) => LinkManager.Instance.addLink(link, true)); rtfMap.map(({ copy, key, field }) => { const replacer = (match: any, attr: string, id: string, offset: any, string: any) => { @@ -816,97 +817,79 @@ export namespace Doc { const mapped = cloneMap.get(id); return href + (mapped ? mapped[Id] : id); }; - const regex = `(${Doc.localServerPath()})([^"]*)`; - const re = new RegExp(regex, 'g'); + const re = new RegExp(`(${Doc.localServerPath()})([^"]*)`, 'g'); const docidsearch = new RegExp('(' + DocsInTextFieldIds.map(exp => `"${exp}":`).join('|') + ')"([^"]+)"', 'g'); copy[key] = new RichTextField(field.Data.replace(docidsearch, replacer).replace(re, replacer2), field.Text); }); + const clonedDocs = [...Array.from(cloneMap.values()), ...linkedDocs]; + clonedDocs.map(clone => Doc.repairClone(clone, cloneMap, repaired)); return { clone: copy, map: cloneMap, linkMap }; } - export async function Zip(doc: Doc) { - // const a = document.createElement("a"); - // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`); - // a.href = url; - // a.download = `DocExport-${this.props.Document[Id]}.zip`; - // a.click(); + export async function Zip(doc: Doc, zipFilename = 'dashExport.zip') { const { clone, map, linkMap } = await Doc.MakeClone(doc); - clone.LINKS = new List<Doc>(Array.from(linkMap.values())); const proms = [] as string[]; function replacer(key: any, value: any) { if (key && ['branchOf', 'cloneOf', 'cursors'].includes(key)) return undefined; else if (value instanceof Doc) { if (key !== 'field' && Number.isNaN(Number(key))) { - const __fields = value[FieldsSym](); - return { id: value[Id], __type: 'Doc', fields: __fields }; - } else { - return { fieldId: value[Id], __type: 'proxy' }; + return { id: value[Id], __type: 'Doc', fields: value[FieldsSym]() }; } - } else if (value instanceof ScriptField) return { script: value.script, __type: 'script' }; - else if (value instanceof RichTextField) return { Data: value.Data, Text: value.Text, __type: 'RichTextField' }; - else if (value instanceof ImageField) { + return { fieldId: value[Id], __type: 'proxy' }; + } else if (value instanceof ImageField) { const extension = value.url.href.replace(/.*\./, ''); proms.push(value.url.href.replace('.' + extension, '_o.' + extension)); return { url: value.url.href, __type: 'image' }; } else if (value instanceof PdfField) { proms.push(value.url.href); return { url: value.url.href, __type: 'pdf' }; - } else if (value instanceof AudioField) return { url: value.url.href, __type: 'audio' }; - else if (value instanceof VideoField) return { url: value.url.href, __type: 'video' }; + } else if (value instanceof AudioField) { + proms.push(value.url.href); + return { url: value.url.href, __type: 'audio' }; + } else if (value instanceof VideoField) { + proms.push(value.url.href); + return { url: value.url.href, __type: 'video' }; + } else if (value instanceof ScriptField) return { script: value.script, __type: 'script' }; + else if (value instanceof RichTextField) return { Data: value.Data, Text: value.Text, __type: 'RichTextField' }; else if (value instanceof WebField) return { url: value.url.href, __type: 'web' }; else if (value instanceof MapField) return { url: value.url.href, __type: 'map' }; else if (value instanceof DateField) return { date: value.toString(), __type: 'date' }; else if (value instanceof ProxyField) return { fieldId: value.fieldId, __type: 'proxy' }; else if (value instanceof Array && key !== 'fields') return { fields: value, __type: 'list' }; else if (value instanceof ComputedField) return { script: value.script, __type: 'computed' }; - else return value; + return value; } const docs: { [id: string]: any } = {}; + const links: { [id: string]: any } = {}; Array.from(map.entries()).forEach(f => (docs[f[0]] = f[1])); - const docString = JSON.stringify({ id: clone[Id], docs }, decycle(replacer)); - - let generateZIP = (proms: string[]) => { - var zip = new JSZip(); - var count = 0; - var zipFilename = 'dashExport.zip'; - - proms - .filter(url => url.startsWith(window.location.origin)) - .forEach((url, i) => { - var filename = proms[i].replace(window.location.origin + '/', '').replace(/\//g, '%%%'); - // loading a file and add it in a zip file - JSZipUtils.getBinaryContent(url, function (err: any, data: any) { - if (err) { - throw err; // or handle the error - } - zip.file(filename, data, { binary: true }); - count++; - if (count == proms.length) { - zip.file('doc.json', docString); - zip.generateAsync({ type: 'blob' }).then(function (content) { - saveAs(content, zipFilename); - }); - } - }); - }); - }; - generateZIP(proms); - const zip = new JSZip(); - - zip.file('doc.json', docString); + Array.from(linkMap.entries()).forEach(l => (links[l[0]] = l[1])); + const jsonDocs = JSON.stringify({ id: clone[Id], docs, links }, decycle(replacer)); - // // Generate a directory within the Zip file structure - // var img = zip.folder("images"); - - // // Add a file to the directory, in this case an image with data URI as contents - // img.file("smile.gif", imgData, {base64: true}); - - // Generate the zip file asynchronously - zip.generateAsync({ type: 'blob' }).then((content: any) => { - // Force down of the Zip file - saveAs(content, doc.title + '.zip'); // glr: Possibly change the name of the document to match the title? - }); + const zip = new JSZip(); + var count = 0; + proms + .filter(url => url.startsWith(window.location.origin)) + .forEach((url, i) => { + // loading a file and add it in a zip file + JSZipUtils.getBinaryContent(url, (err: any, data: any) => { + if (err) throw err; // or handle the error + // // Generate a directory within the Zip file structure + // const assets = zip.folder("assets"); + // assets.file(filename, data, {binary: true}); + const assetPathOnServer = proms[i].replace(window.location.origin + '/', '').replace(/\//g, '%%%'); + zip.file(assetPathOnServer, data, { binary: true }); + if (++count == proms.length) { + zip.file('docs.json', jsonDocs); + zip.generateAsync({ type: 'blob' }).then(content => saveAs(content, zipFilename)); + // const a = document.createElement("a"); + // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`); + // a.href = url; + // a.download = `DocExport-${this.props.Document[Id]}.zip`; + // a.click(); + } + }); + }); } const _pendingMap: Map<string, boolean> = new Map(); @@ -963,7 +946,7 @@ export namespace Doc { // otherwise, it just returns the childDoc export function GetLayoutDataDocPair(containerDoc: Doc, containerDataDoc: Opt<Doc>, childDoc: Doc) { if (!childDoc || childDoc instanceof Promise || !Doc.GetProto(childDoc)) { - console.log('No, no, no!'); + console.log('Warning: GetLayoutDataDocPair childDoc not defined'); return { layout: childDoc, data: childDoc }; } const resolvedDataDoc = Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!childDoc.isTemplateDoc && !childDoc.isTemplateForField) ? undefined : containerDataDoc; @@ -1198,7 +1181,7 @@ export namespace Doc { return doc[StrCast(doc.layoutKey, 'layout')]; } export function LayoutFieldKey(doc: Doc): string { - return StrCast(Doc.Layout(doc)[StrCast(doc.layoutKey, 'layout')]).split("'")[1]; // bcz: TODO check on this . used to always reference 'layout', now it uses the layout speicfied by the current layoutKey + return StrCast(Doc.Layout(doc).layout).split("'")[1]; // bcz: TODO check on this . used to always reference 'layout', now it uses the layout speicfied by the current layoutKey } export function NativeAspect(doc: Doc, dataDoc?: Doc, useDim?: boolean) { return Doc.NativeWidth(doc, dataDoc, useDim) / (Doc.NativeHeight(doc, dataDoc, useDim) || 1); @@ -1412,7 +1395,7 @@ export namespace Doc { return vals.some(v => v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring } const fieldStr = Field.toString(fieldVal as Field); - return fieldStr.includes(value); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring + return fieldStr.includes(value) || (value === String.fromCharCode(127) + '--undefined--' && fieldVal === undefined); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring } export function deiconifyView(doc: Doc) { @@ -1553,20 +1536,26 @@ export namespace Doc { } } - export async function importDocument(file: File) { + /// + // imports a previously exported zip file which contains a set of documents and their assets (eg, images, videos) + // the 'remap' parameter determines whether the ids of the documents loaded should be kept as they were, or remapped to new ids + // If they are not remapped, loading the file will overwrite any existing documents with those ids + // + export async function importDocument(file: File, remap = false) { const upload = Utils.prepend('/uploadDoc'); const formData = new FormData(); if (file) { formData.append('file', file); - formData.append('remap', 'true'); + formData.append('remap', remap.toString()); const response = await fetch(upload, { method: 'POST', body: formData }); const json = await response.json(); - console.log(json); if (json !== 'error') { - await DocServer.GetRefFields(json.docids as string[]); + const docs = await DocServer.GetRefFields(json.docids as string[]); const doc = DocCast(await DocServer.GetRefField(json.id)); - (await DocListCastAsync(doc?.LINKS))?.forEach(link => LinkManager.Instance.addLink(link)); - doc.LINKS = undefined; + const links = await DocServer.GetRefFields(json.linkids as string[]); + Array.from(Object.keys(links)) + .map(key => links[key]) + .forEach(link => link instanceof Doc && LinkManager.Instance.addLink(link)); return doc; } } @@ -1714,8 +1703,11 @@ export namespace Doc { } } +export function IdToDoc(id: string) { + return DocCast(DocServer.GetCachedRefField(id)); +} ScriptingGlobals.add(function idToDoc(id: string): any { - return DocServer.GetCachedRefField(id); + return IdToDoc(id); }); ScriptingGlobals.add(function renameAlias(doc: any) { return StrCast(Doc.GetProto(doc).title).replace(/\([0-9]*\)/, '') + `(${doc.aliasNumber})`; diff --git a/src/fields/util.ts b/src/fields/util.ts index 70d9ed61f..92f3a69eb 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -107,8 +107,11 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number redo: () => (receiver[prop] = value), undo: () => { const wasUpdate = receiver[UpdatingFromServer]; + const wasForce = receiver[ForceServerWrite]; + receiver[ForceServerWrite] = true; // needed since writes aren't propagated to server if UpdatingFromServerIsSet receiver[UpdatingFromServer] = true; // needed if the event caused ACL's to change such that the doc is otherwise no longer editable. receiver[prop] = curValue; + receiver[ForceServerWrite] = wasForce; receiver[UpdatingFromServer] = wasUpdate; }, prop: prop?.toString(), diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index 6e28268a9..74c06b4a6 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -180,13 +180,9 @@ export default class UploadManager extends ApiManager { const ids: { [id: string]: string } = {}; let remap = true; const getId = (id: string): string => { - if (!remap) return id; - if (id.endsWith('Proto')) return id; - if (id in ids) { - return ids[id]; - } else { - return (ids[id] = v4()); - } + if (!remap || id.endsWith('Proto')) return id; + if (id in ids) return ids[id]; + return (ids[id] = v4()); }; const mapFn = (doc: any) => { if (doc.id) { @@ -229,6 +225,7 @@ export default class UploadManager extends ApiManager { remap = fields.remap !== 'false'; let id: string = ''; let docids: string[] = []; + let linkids: string[] = []; try { for (const name in files) { const f = files[name]; @@ -255,27 +252,22 @@ export default class UploadManager extends ApiManager { console.log(e); } }); - const json = zip.getEntry('doc.json'); + const json = zip.getEntry('docs.json'); try { const data = JSON.parse(json.getData().toString('utf8'), retrocycle()); - const datadocs = data.docs; + const { docs, links } = data; id = getId(data.id); - const docs = Object.keys(datadocs).map(key => datadocs[key]); - docs.forEach(mapFn); - docids = docs.map(doc => doc.id); + const rdocs = Object.keys(docs).map(key => docs[key]); + const ldocs = Object.keys(links).map(key => links[key]); + [...rdocs, ...ldocs].forEach(mapFn); + docids = rdocs.map(doc => doc.id); + linkids = ldocs.map(link => link.id); await Promise.all( - docs.map( - (doc: any) => + [...rdocs, ...ldocs].map( + doc => new Promise<void>(res => { - Database.Instance.replace( - doc.id, - doc, - (err, r) => { - err && console.log(err); - res(); - }, - true - ); + // overwrite mongo doc with json doc contents + Database.Instance.replace(doc.id, doc, (err, r) => res(err && console.log(err)), true); }) ) ); @@ -285,7 +277,7 @@ export default class UploadManager extends ApiManager { unlink(path_2, () => {}); } SolrManager.update(); - res.send(JSON.stringify({ id, docids } || 'error')); + res.send(JSON.stringify({ id, docids, linkids } || 'error')); } catch (e) { console.log(e); } diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index f461cf3fa..070d49ec3 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -70,7 +70,14 @@ export namespace DashUploadUtils { // make a list of paths to create the ordered text file for ffmpeg const filePathsText = filePaths.map(filePath => `file '${filePath}'`).join('\n'); // write the text file to the file system - writeFile(textFilePath, filePathsText, err => console.log(err)); + await new Promise<void>((res, reject) => + writeFile(textFilePath, filePathsText, err => { + if (err) { + reject(); + console.log(err); + } else res(); + }) + ); // make output file name based on timestamp const outputFileName = `output-${Utils.GenerateGuid()}.mp4`; @@ -86,7 +93,10 @@ export namespace DashUploadUtils { .outputOptions('-c copy') //.videoCodec("copy") .save(outputFilePath) - .on('error', reject) + .on('error', (err: any) => { + console.log(err); + reject(); + }) .on('end', resolve); }); diff --git a/src/server/websocket.ts b/src/server/websocket.ts index a11d20cfa..2acdaa5a3 100644 --- a/src/server/websocket.ts +++ b/src/server/websocket.ts @@ -51,7 +51,7 @@ export namespace WebSocket { next(); }); - socket.emit(MessageStore.UpdateStats.Message, DashStats.getUpdatedStatsBundle()) + socket.emit(MessageStore.UpdateStats.Message, DashStats.getUpdatedStatsBundle()); // convenience function to log server messages on the client function log(message?: any, ...optionalParams: any[]) { @@ -104,14 +104,12 @@ export namespace WebSocket { socket.on('disconnect', function () { let currentUser = socketMap.get(socket); if (!(currentUser === undefined)) { - let currentUsername = currentUser.split(' ')[0] + let currentUsername = currentUser.split(' ')[0]; DashStats.logUserLogout(currentUsername, socket); - delete timeMap[currentUsername] + delete timeMap[currentUsername]; } }); - - Utils.Emit(socket, MessageStore.Foo, 'handshooken'); Utils.AddServerHandler(socket, MessageStore.Bar, guid => barReceived(socket, guid)); @@ -146,10 +144,10 @@ export namespace WebSocket { }; }); - setInterval(function() { + setInterval(function () { // Utils.Emit(socket, MessageStore.UpdateStats, DashStats.getUpdatedStatsBundle()); - io.emit(MessageStore.UpdateStats.Message, DashStats.getUpdatedStatsBundle()) + io.emit(MessageStore.UpdateStats.Message, DashStats.getUpdatedStatsBundle()); }, DashStats.SAMPLING_INTERVAL); } @@ -193,7 +191,7 @@ export namespace WebSocket { } function barReceived(socket: SocketIO.Socket, userEmail: string) { - clients[userEmail] = new Client(userEmail.toString()); + clients[userEmail] = new Client(userEmail.toString()); const currentdate = new Date(); const datetime = currentdate.getDate() + '/' + (currentdate.getMonth() + 1) + '/' + currentdate.getFullYear() + ' @ ' + currentdate.getHours() + ':' + currentdate.getMinutes() + ':' + currentdate.getSeconds(); console.log(blue(`user ${userEmail} has connected to the web socket at: ${datetime}`)); @@ -326,8 +324,7 @@ export namespace WebSocket { const remListItems = diff.diff.$set[updatefield].fields; const curList = (curListItems as any)?.fields?.[updatefield.replace('fields.', '')]?.fields.filter((f: any) => f !== null) || []; diff.diff.$set[updatefield].fields = curList?.filter( - (curItem: any) => !remListItems.some((remItem: any) => (remItem.fieldId ? remItem.fieldId === curItem.fieldId : - remItem.heading ? remItem.heading === curItem.heading : remItem === curItem)) + (curItem: any) => !remListItems.some((remItem: any) => (remItem.fieldId ? remItem.fieldId === curItem.fieldId : remItem.heading ? remItem.heading === curItem.heading : remItem === curItem)) ); const sendBack = diff.diff.length !== diff.diff.$set[updatefield].fields.length; delete diff.diff.length; @@ -373,9 +370,11 @@ export namespace WebSocket { var CurUser: string | undefined = undefined; function UpdateField(socket: Socket, diff: Diff) { - let currentUsername = socketMap.get(socket)!.split(' ')[0]; + const curUser = socketMap.get(socket); + if (!curUser) return; + let currentUsername = curUser.split(' ')[0]; userOperations.set(currentUsername, userOperations.get(currentUsername) !== undefined ? userOperations.get(currentUsername)! + 1 : 0); - + if (CurUser !== socketMap.get(socket)) { CurUser = socketMap.get(socket); console.log('Switch User: ' + CurUser); |