From 73f709846365cb266c8489678d5c6713c0c43158 Mon Sep 17 00:00:00 2001 From: alyssaf16 Date: Tue, 5 Mar 2024 17:23:33 -0500 Subject: difference between flashcard and comparison node --- src/client/documents/Documents.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 2d2f5fe4a..f334e958a 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -265,6 +265,7 @@ export class DocumentOptions { _layout_fitContentsToBox?: BOOLt = new BoolInfo('whether a freeformview should zoom/scale to create a shrinkwrapped view of its content'); _layout_fieldKey?: STRt = new StrInfo('the field key containing the current layout definition', false); _layout_enableAltContentUI?: BOOLt = new BoolInfo('whether to show alternate content button'); + _layout_isFlashcard?: BOOLt = new BoolInfo('whether comparison node should be displayed as a flashcard'); _layout_showTitle?: string; // field name to display in header (:hover is an optional suffix) _layout_showSidebar?: BOOLt = new BoolInfo('whether an annotationsidebar should be displayed for text docuemnts'); _layout_showCaption?: string; // which field to display in the caption area. leave empty to have no caption @@ -728,7 +729,16 @@ export namespace Docs { { data: '', layout: { view: ComparisonBox, dataField: defaultDataKey }, - options: { backgroundColor: 'gray', dropAction: 'move', waitForDoubleClickToClick: 'always', layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true, systemIcon: 'BsLayoutSplit' }, + options: { + _layout_isFlashcard: false, + backgroundColor: 'gray', + dropAction: 'move', + waitForDoubleClickToClick: 'always', + layout_reflowHorizontal: true, + layout_reflowVertical: true, + layout_nativeDimEditable: true, + systemIcon: 'BsLayoutSplit', + }, }, ], [ -- cgit v1.2.3-70-g09d2 From ed5a7755eba7563c01bc3cc40840d0ebb6cd8826 Mon Sep 17 00:00:00 2001 From: Zachary Zhang Date: Wed, 6 Mar 2024 19:46:09 -0500 Subject: add diagram document --- package-lock.json | 684 +++++++++++++++++++++++- package.json | 1 + src/.DS_Store | Bin 10244 -> 10244 bytes src/client/documents/DocumentTypes.ts | 1 + src/client/documents/Documents.ts | 12 +- src/client/util/CurrentUserUtils.ts | 7 +- src/client/views/nodes/DiagramBox.tsx | 86 +++ src/client/views/nodes/DocumentContentsView.tsx | 2 + 8 files changed, 788 insertions(+), 5 deletions(-) create mode 100644 src/client/views/nodes/DiagramBox.tsx (limited to 'src/client/documents') diff --git a/package-lock.json b/package-lock.json index 1f1c994c3..4cc6d8d2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -128,6 +128,7 @@ "mathquill": "^0.10.1-a", "md5-file": "^5.0.0", "memorystream": "^0.3.1", + "mermaid": "^10.8.0", "mobile-detect": "^1.4.5", "mobx": "^6.12.0", "mobx-react": "^9.1.0", @@ -2224,6 +2225,11 @@ "node": ">=6.9.0" } }, + "node_modules/@braintree/sanitize-url": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz", + "integrity": "sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==" + }, "node_modules/@bundled-es-modules/pdfjs-dist": { "version": "3.6.172-alpha.1", "resolved": "https://registry.npmjs.org/@bundled-es-modules/pdfjs-dist/-/pdfjs-dist-3.6.172-alpha.1.tgz", @@ -8784,8 +8790,7 @@ "node_modules/@types/d3-scale-chromatic": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.3.tgz", - "integrity": "sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==", - "dev": true + "integrity": "sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==" }, "node_modules/@types/d3-selection": { "version": "3.0.10", @@ -14803,6 +14808,14 @@ "node": ">= 0.10" } }, + "node_modules/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "dependencies": { + "layout-base": "^1.0.0" + } + }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -15107,6 +15120,29 @@ "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==" }, + "node_modules/cytoscape": { + "version": "3.28.1", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.28.1.tgz", + "integrity": "sha512-xyItz4O/4zp9/239wCcH8ZcFuuZooEeF8KHRmzjDfGdXsj3OG9MFSMA0pJE0uX3uCN/ygof6hHf4L7lst+JaDg==", + "dependencies": { + "heap": "^0.2.6", + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "dependencies": { + "cose-base": "^1.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, "node_modules/D": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/D/-/D-1.0.0.tgz", @@ -15387,6 +15423,41 @@ "node": ">=12" } }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/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==", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==" + }, "node_modules/d3-scale": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", @@ -15512,6 +15583,15 @@ "node": ">=12" } }, + "node_modules/dagre-d3-es": { + "version": "7.0.10", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.10.tgz", + "integrity": "sha512-qTCQmEhcynucuaZgY5/+ti3X/rnszKZhEQH/ZdWdtP1tA/y3VoHJzcVrO9pjjJCNpigfscAtoUB5ONcd2wNn0A==", + "dependencies": { + "d3": "^7.8.2", + "lodash-es": "^4.17.21" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -15566,6 +15646,11 @@ "url": "https://opencollective.com/date-fns" } }, + "node_modules/dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -15928,7 +16013,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true, "engines": { "node": ">=0.3.1" } @@ -16031,6 +16115,11 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, + "node_modules/dompurify": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.9.tgz", + "integrity": "sha512-uyb4NDIvQ3hRn6NiC+SIFaP4mJ/MdXlvtunaqK9Bn6dD3RuB/1S/gasEjDHD8eiaqdSael2vBv+hOs7Y+jhYOQ==" + }, "node_modules/domutils": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", @@ -16116,6 +16205,11 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.670.tgz", "integrity": "sha512-hcijYOWjOtjKrKPtNA6tuLlA/bTLO3heFG8pQA6mLpq7dRydSWicXova5lyxDzp1iVJaYhK7J2OQlGE52KYn7A==" }, + "node_modules/elkjs": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.9.2.tgz", + "integrity": "sha512-2Y/RaA1pdgSHpY0YG4TYuYCD2wh97CRvu22eLG3Kz0pgQ/6KbIFTxsTnDc4MH/6hFlg2L/9qXrDMG0nMjP63iw==" + }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -19980,6 +20074,11 @@ "he": "bin/he" } }, + "node_modules/heap": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz", + "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==" + }, "node_modules/hexoid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", @@ -22098,6 +22197,11 @@ "json-buffer": "3.0.1" } }, + "node_modules/khroma": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" + }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -22114,6 +22218,14 @@ "graceful-fs": "^4.1.9" } }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "engines": { + "node": ">=6" + } + }, "node_modules/kruptein": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/kruptein/-/kruptein-3.0.6.tgz", @@ -22204,6 +22316,11 @@ "shell-quote": "^1.8.1" } }, + "node_modules/layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==" + }, "node_modules/lazystream": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", @@ -23033,6 +23150,513 @@ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" }, + "node_modules/mermaid": { + "version": "10.8.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-10.8.0.tgz", + "integrity": "sha512-9CzfSreRjdDJxX796+jW4zjEq0DVw5xVF0nWsqff8OTbrt+ml0TZ5PyYUjjUZJa2NYxYJZZXewEquxGiM8qZEA==", + "dependencies": { + "@braintree/sanitize-url": "^6.0.1", + "@types/d3-scale": "^4.0.3", + "@types/d3-scale-chromatic": "^3.0.0", + "cytoscape": "^3.28.1", + "cytoscape-cose-bilkent": "^4.1.0", + "d3": "^7.4.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.10", + "dayjs": "^1.11.7", + "dompurify": "^3.0.5", + "elkjs": "^0.9.0", + "khroma": "^2.0.0", + "lodash-es": "^4.17.21", + "mdast-util-from-markdown": "^1.3.0", + "non-layered-tidy-tree-layout": "^2.0.2", + "stylis": "^4.1.3", + "ts-dedent": "^2.2.0", + "uuid": "^9.0.0", + "web-worker": "^1.2.0" + } + }, + "node_modules/mermaid/node_modules/@types/mdast": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/mermaid/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/mermaid/node_modules/mdast-util-from-markdown": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", + "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==", + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "mdast-util-to-string": "^3.1.0", + "micromark": "^3.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-decode-string": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "unist-util-stringify-position": "^3.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mermaid/node_modules/mdast-util-to-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", + "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", + "dependencies": { + "@types/mdast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mermaid/node_modules/micromark": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz", + "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "micromark-core-commonmark": "^1.0.1", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/mermaid/node_modules/micromark-core-commonmark": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", + "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-factory-destination": "^1.0.0", + "micromark-factory-label": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-factory-title": "^1.0.0", + "micromark-factory-whitespace": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-html-tag-name": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/mermaid/node_modules/micromark-factory-destination": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", + "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-factory-label": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", + "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/mermaid/node_modules/micromark-factory-space": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-factory-title": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", + "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-factory-whitespace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", + "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-util-chunked": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", + "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-util-classify-character": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", + "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-util-combine-extensions": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", + "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-util-decode-numeric-character-reference": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", + "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-util-decode-string": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz", + "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-util-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", + "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/mermaid/node_modules/micromark-util-html-tag-name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", + "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/mermaid/node_modules/micromark-util-normalize-identifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", + "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-util-resolve-all": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", + "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-util-sanitize-uri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", + "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/mermaid/node_modules/micromark-util-subtokenize": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", + "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/mermaid/node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/mermaid/node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/mermaid/node_modules/unist-util-stringify-position": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", + "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -24060,6 +24684,14 @@ "resolved": "https://registry.npmjs.org/mr-parser/-/mr-parser-0.2.1.tgz", "integrity": "sha512-hug+mpbSSKnH13rFqy3zm+XiG+QTStiDAgMTHK355TIstQE0qBkBtSJsa5YHP94AuarVX9b/4dcebdTRZ9YiEw==" }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "engines": { + "node": ">=4" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -24405,6 +25037,11 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/non-layered-tidy-tree-layout": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz", + "integrity": "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==" + }, "node_modules/nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", @@ -29955,6 +30592,17 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/safe-array-concat": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.0.tgz", @@ -31878,6 +32526,14 @@ "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", "integrity": "sha512-6C5h3CE+0qjGp+YKYTs74xR0k/Nw/ePtl/Lp6CCf44hqBQ66qnH1sDFR5mV/Gc48EsrHLB53lCFSffQCkka3kg==" }, + "node_modules/ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "engines": { + "node": ">=6.10" + } + }, "node_modules/ts-loader": { "version": "9.5.1", "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz", @@ -33044,6 +33700,23 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/uvu": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", + "dependencies": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3" + }, + "bin": { + "uvu": "bin.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/v8-compile-cache": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", @@ -33240,6 +33913,11 @@ "node": ">= 8" } }, + "node_modules/web-worker": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.3.0.tgz", + "integrity": "sha512-BSR9wyRsy/KOValMgd5kMyr3JzpdeoR9KVId8u5GVlTTAtNChlsE4yTxeY7zMdNSyOmoKBv8NH2qeRY9Tg+IaA==" + }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", diff --git a/package.json b/package.json index b27d50d54..cbe32d9ca 100644 --- a/package.json +++ b/package.json @@ -211,6 +211,7 @@ "mathquill": "^0.10.1-a", "md5-file": "^5.0.0", "memorystream": "^0.3.1", + "mermaid": "^10.8.0", "mobile-detect": "^1.4.5", "mobx": "^6.12.0", "mobx-react": "^9.1.0", diff --git a/src/.DS_Store b/src/.DS_Store index bb607fbc3..e1d584ba0 100644 Binary files a/src/.DS_Store and b/src/.DS_Store differ diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts index 1123bcac9..570d2e9c8 100644 --- a/src/client/documents/DocumentTypes.ts +++ b/src/client/documents/DocumentTypes.ts @@ -12,6 +12,7 @@ export enum DocumentType { REC = 'recording', PDF = 'pdf', INK = 'ink', + DIAGRAM='diagram', SCREENSHOT = 'screenshot', FONTICON = 'fonticonbox', SEARCH = 'search', // search query diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 2d2f5fe4a..ade5e718c 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -55,6 +55,7 @@ import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox'; import { PresBox } from '../views/nodes/trails/PresBox'; import { PresElementBox } from '../views/nodes/trails/PresElementBox'; import { VideoBox } from '../views/nodes/VideoBox'; +import { DiagramBox } from '../views/nodes/DiagramBox'; import { WebBox } from '../views/nodes/WebBox'; import { SearchBox } from '../views/search/SearchBox'; import { CollectionViewType, DocumentType } from './DocumentTypes'; @@ -658,6 +659,13 @@ export namespace Docs { }, }, ], + [ + DocumentType.DIAGRAM, + { + layout: { view: DiagramBox, dataField: defaultDataKey }, + options: { _height: 300, _layout_fitWidth: true, layout_nativeDimEditable: true, layout_reflowVertical: true, waitForDoubleClickToClick: 'always', systemIcon: 'BsGlobe' }, + }, + ], [ DocumentType.BUTTON, { @@ -1025,7 +1033,9 @@ export namespace Docs { export function ComparisonDocument(options: DocumentOptions = { title: 'Comparison Box' }) { return InstanceFromProto(Prototypes.get(DocumentType.COMPARISON), undefined, options); } - + export function DiagramDocument(options: DocumentOptions = { title: 'bruh box' }) { + return InstanceFromProto(Prototypes.get(DocumentType.DIAGRAM), undefined, options); + } export function AudioDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) { return InstanceFromProto(Prototypes.get(DocumentType.AUDIO), new AudioField(url), options, undefined, undefined, undefined, overwriteDoc); } diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index d61a40ffa..8275ec6b8 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -248,6 +248,7 @@ export class CurrentUserUtils { {key: "Collection", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 150, _height: 100, _layout_fitWidth: true }}, {key: "Webpage", creator: opts => Docs.Create.WebDocument("",opts), opts: { _width: 400, _height: 512, _nativeWidth: 850, data_useCors: true, }}, {key: "Comparison", creator: Docs.Create.ComparisonDocument, opts: { _width: 300, _height: 300 }}, + {key: "Diagram", creator: Docs.Create.DiagramDocument, opts: { _width: 300, _height: 300 }}, {key: "Audio", creator: opts => Docs.Create.AudioDocument(nullAudio, opts),opts: { _width: 200, _height: 100, }}, {key: "Map", creator: opts => Docs.Create.MapDocument([], opts), opts: { _width: 800, _height: 600, _layout_fitWidth: true, }}, {key: "Screengrab", creator: Docs.Create.ScreenshotDocument, opts: { _width: 400, _height: 200 }}, @@ -266,7 +267,10 @@ export class CurrentUserUtils { }, funcs: {title: 'this.text?.Text'}}, ]; - emptyThings.forEach(thing => DocUtils.AssignDocField(doc, "empty"+thing.key, (opts) => thing.creator(opts), {...standardOps(thing.key), ...thing.opts}, undefined, thing.scripts, thing.funcs)); + emptyThings.forEach( + thing =>{ DocUtils.AssignDocField(doc, "empty"+thing.key, (opts) => thing.creator(opts), {...standardOps(thing.key), ...thing.opts}, undefined, thing.scripts, thing.funcs); + console.log(thing.key) + }); return [ { toolTip: "Tap or drag to create a note", title: "Note", icon: "sticky-note", dragFactory: doc.emptyNote as Doc, clickFactory: DocCast(doc.emptyNote)}, @@ -277,6 +281,7 @@ export class CurrentUserUtils { { toolTip: "Tap or drag to create a collection", title: "Col", icon: "folder", dragFactory: doc.emptyCollection as Doc, clickFactory: DocCast(doc.emptyTab)}, { toolTip: "Tap or drag to create a webpage", title: "Web", icon: "globe-asia", dragFactory: doc.emptyWebpage as Doc, clickFactory: DocCast(doc.emptyWebpage)}, { toolTip: "Tap or drag to create a comparison box", title: "Compare", icon: "columns", dragFactory: doc.emptyComparison as Doc, clickFactory: DocCast(doc.emptyComparison)}, + { toolTip: "Tap or drag to create a diagram", title: "Diagram", icon: "circle", dragFactory: doc.emptyDiagram as Doc, clickFactory: DocCast(doc.emptyDiagram)}, { toolTip: "Tap or drag to create an audio recorder", title: "Audio", icon: "microphone", dragFactory: doc.emptyAudio as Doc, clickFactory: DocCast(doc.emptyAudio), openFactoryLocation: OpenWhere.overlay}, { toolTip: "Tap or drag to create a map", title: "Map", icon: "map-marker-alt", dragFactory: doc.emptyMap as Doc, clickFactory: DocCast(doc.emptyMap)}, { toolTip: "Tap or drag to create a screen grabber", title: "Grab", icon: "photo-video", dragFactory: doc.emptyScreengrab as Doc, clickFactory: DocCast(doc.emptyScreengrab), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}}, diff --git a/src/client/views/nodes/DiagramBox.tsx b/src/client/views/nodes/DiagramBox.tsx new file mode 100644 index 000000000..e2af0b670 --- /dev/null +++ b/src/client/views/nodes/DiagramBox.tsx @@ -0,0 +1,86 @@ +import { action, computed, makeObservable, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { emptyFunction, setupMoveUpEvents } from '../../../Utils'; +import { Doc, Opt } from '../../../fields/Doc'; +import { DocCast, NumCast, StrCast } from '../../../fields/Types'; +import { Docs } from '../../documents/Documents'; +import { DragManager } from '../../util/DragManager'; +import { undoBatch } from '../../util/UndoManager'; +import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; +import { StyleProp } from '../StyleProvider'; +import './ComparisonBox.scss'; +import { FieldView, FieldViewProps } from './FieldView'; +import { PinProps, PresBox } from './trails'; +import mermaid from 'mermaid'; + +interface MermaidProps { + chart: String; + } + +class Mermaid extends React.Component { + componentDidMount() { + mermaid.contentLoaded(); + } + render() { + return
{this.props.chart}
; + } + } + +@observer +export class DiagramBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { + + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(DiagramBox, fieldKey); + } + constructor(props: FieldViewProps) { + super(props); + makeObservable(this); + mermaid.initialize({ startOnLoad: true }); + } + + @observable _animating = ''; + + @computed get clipWidth() { + return NumCast(this.layoutDoc[this.clipWidthKey], 50); + } + get clipWidthKey() { + return '_' + this._props.fieldKey + '_clipWidth'; + } + + componentDidMount() { + this._props.setContentViewBox?.(this); + } + getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { + const anchor = Docs.Create.ConfigDocument({ + title: 'CompareAnchor:' + this.Document.title, + // set presentation timing properties for restoring view + presentation_transition: 1000, + annotationOn: this.Document, + }); + if (anchor) { + if (!addAsAnnotation) anchor.backgroundColor = 'transparent'; + /* addAsAnnotation &&*/ this.addDocument(anchor); + PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), clippable: true } }, this.Document); + return anchor; + } + return this.Document; + }; + docStyleProvider = (doc: Opt, props: Opt, property: string): any => { + if (property === StyleProp.PointerEvents) return 'none'; + return this._props.styleProvider?.(doc, props, property); + }; + _closeRef = React.createRef(); + render() { + return ( +
+ B; + B-->C; + B-->D[hi]; + `}/> +
+ ); + } +} diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 07e179246..040c8364d 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -23,6 +23,7 @@ import { DashWebRTCVideo } from '../webcam/DashWebRTCVideo'; import { YoutubeBox } from './../../apis/youtube/YoutubeBox'; import { AudioBox } from './AudioBox'; import { ComparisonBox } from './ComparisonBox'; +import { DiagramBox } from './DiagramBox'; import { DataVizBox } from './DataVizBox/DataVizBox'; import './DocumentView.scss'; import { EquationBox } from './EquationBox'; @@ -263,6 +264,7 @@ export class DocumentContentsView extends ObservableReactComponent Date: Sun, 10 Mar 2024 13:10:43 -0400 Subject: test --- src/client/documents/Documents.ts | 1 + src/client/util/CurrentUserUtils.ts | 5 +- src/client/views/nodes/DiagramBox.tsx | 88 ++++++++++++++++------------------- 3 files changed, 45 insertions(+), 49 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index ade5e718c..a6e229ba7 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -252,6 +252,7 @@ export class DocumentOptions { layout_hideLinkButton?: BOOLt = new BoolInfo('whether the blue link counter button should be hidden'); layout_hideDecorationTitle?: BOOLt = new BoolInfo('whether to suppress the document decortations title when selected'); _layout_hideContextMenu?: BOOLt = new BoolInfo('whether the context menu can be shown'); + layout_diagramEditor?: STRt = new StrInfo("specify the JSX string for a diagram editor view") layout_borderRounding?: string; _layout_modificationDate?: DATEt = new DateInfo('last modification date of doc layout', false); _layout_nativeDimEditable?: BOOLt = new BoolInfo('native dimensions can be modified using document decoration reizers', false); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 8275ec6b8..0279ca2c4 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -31,6 +31,7 @@ import { ScriptingGlobals } from "./ScriptingGlobals"; import { ColorScheme } from "./SettingsManager"; import { SnappingManager } from "./SnappingManager"; import { UndoManager } from "./UndoManager"; +import { CollectionView } from "../views/collections/CollectionView"; interface Button { // DocumentOptions fields a button can set @@ -248,7 +249,7 @@ export class CurrentUserUtils { {key: "Collection", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 150, _height: 100, _layout_fitWidth: true }}, {key: "Webpage", creator: opts => Docs.Create.WebDocument("",opts), opts: { _width: 400, _height: 512, _nativeWidth: 850, data_useCors: true, }}, {key: "Comparison", creator: Docs.Create.ComparisonDocument, opts: { _width: 300, _height: 300 }}, - {key: "Diagram", creator: Docs.Create.DiagramDocument, opts: { _width: 300, _height: 300 }}, + {key: "Diagram", creator: Docs.Create.DiagramDocument, opts: { _width: 300, _height: 300, _type_collection: CollectionViewType.Freeform, layout_diagramEditor: CollectionView.LayoutString("data") }, scripts: { onPaint: `toggleDetail(documentView, "diagramEditor","")`}}, {key: "Audio", creator: opts => Docs.Create.AudioDocument(nullAudio, opts),opts: { _width: 200, _height: 100, }}, {key: "Map", creator: opts => Docs.Create.MapDocument([], opts), opts: { _width: 800, _height: 600, _layout_fitWidth: true, }}, {key: "Screengrab", creator: Docs.Create.ScreenshotDocument, opts: { _width: 400, _height: 200 }}, @@ -281,7 +282,7 @@ export class CurrentUserUtils { { toolTip: "Tap or drag to create a collection", title: "Col", icon: "folder", dragFactory: doc.emptyCollection as Doc, clickFactory: DocCast(doc.emptyTab)}, { toolTip: "Tap or drag to create a webpage", title: "Web", icon: "globe-asia", dragFactory: doc.emptyWebpage as Doc, clickFactory: DocCast(doc.emptyWebpage)}, { toolTip: "Tap or drag to create a comparison box", title: "Compare", icon: "columns", dragFactory: doc.emptyComparison as Doc, clickFactory: DocCast(doc.emptyComparison)}, - { toolTip: "Tap or drag to create a diagram", title: "Diagram", icon: "circle", dragFactory: doc.emptyDiagram as Doc, clickFactory: DocCast(doc.emptyDiagram)}, + { toolTip: "Tap or drag to create a diagram", title: "Diagram", icon: "tree", dragFactory: doc.emptyDiagram as Doc, clickFactory: DocCast(doc.emptyDiagram)}, { toolTip: "Tap or drag to create an audio recorder", title: "Audio", icon: "microphone", dragFactory: doc.emptyAudio as Doc, clickFactory: DocCast(doc.emptyAudio), openFactoryLocation: OpenWhere.overlay}, { toolTip: "Tap or drag to create a map", title: "Map", icon: "map-marker-alt", dragFactory: doc.emptyMap as Doc, clickFactory: DocCast(doc.emptyMap)}, { toolTip: "Tap or drag to create a screen grabber", title: "Grab", icon: "photo-video", dragFactory: doc.emptyScreengrab as Doc, clickFactory: DocCast(doc.emptyScreengrab), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}}, diff --git a/src/client/views/nodes/DiagramBox.tsx b/src/client/views/nodes/DiagramBox.tsx index e2af0b670..376dff15d 100644 --- a/src/client/views/nodes/DiagramBox.tsx +++ b/src/client/views/nodes/DiagramBox.tsx @@ -1,85 +1,79 @@ -import { action, computed, makeObservable, observable } from 'mobx'; +import { makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { emptyFunction, setupMoveUpEvents } from '../../../Utils'; -import { Doc, Opt } from '../../../fields/Doc'; -import { DocCast, NumCast, StrCast } from '../../../fields/Types'; -import { Docs } from '../../documents/Documents'; -import { DragManager } from '../../util/DragManager'; -import { undoBatch } from '../../util/UndoManager'; import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; import { StyleProp } from '../StyleProvider'; import './ComparisonBox.scss'; import { FieldView, FieldViewProps } from './FieldView'; import { PinProps, PresBox } from './trails'; import mermaid from 'mermaid'; +import { Doc, DocListCast } from '../../../fields/Doc'; +import { List } from '../../../fields/List'; interface MermaidProps { - chart: String; - } + chart: String; +} class Mermaid extends React.Component { componentDidMount() { - mermaid.contentLoaded(); + mermaid.contentLoaded(); } render() { - return
{this.props.chart}
; + return
{this.props.chart}
; } - } +} @observer export class DiagramBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { - + @observable chartContent: string = ''; + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DiagramBox, fieldKey); } constructor(props: FieldViewProps) { super(props); makeObservable(this); - mermaid.initialize({ startOnLoad: true }); + //this.createMermaidCode(); + this.chartContent = 'graph LR;A-->B;B-->C; B-->D[hello];'; } - @observable _animating = ''; - - @computed get clipWidth() { - return NumCast(this.layoutDoc[this.clipWidthKey], 50); - } - get clipWidthKey() { - return '_' + this._props.fieldKey + '_clipWidth'; - } - componentDidMount() { this._props.setContentViewBox?.(this); + mermaid.initialize({ startOnLoad: true }); } - getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { - const anchor = Docs.Create.ConfigDocument({ - title: 'CompareAnchor:' + this.Document.title, - // set presentation timing properties for restoring view - presentation_transition: 1000, - annotationOn: this.Document, - }); - if (anchor) { - if (!addAsAnnotation) anchor.backgroundColor = 'transparent'; - /* addAsAnnotation &&*/ this.addDocument(anchor); - PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), clippable: true } }, this.Document); - return anchor; + createMermaidCode = (): void => { + if (this.Document.data instanceof List) { + let docArray: Doc[] = DocListCast(this.Document.data); + let mermaidCode = 'graph LR;'; + docArray.map(doc => { + if (doc.title == 'rectangle') { + DocListCast(this.Document.data).map(lineDoc => { + if ((lineDoc.title == 'stroke' || lineDoc.title == 'line') && typeof lineDoc.x === 'number' && typeof doc.x === 'number' && typeof doc.width === 'number'&& typeof doc.height === 'number'&& typeof doc.y === 'number'&& typeof lineDoc.y === 'number') { + if (lineDoc.x < doc.x + doc.width + (doc.width + doc.x) * 0.1 && lineDoc.x > doc.x&&lineDoc.y>doc.y&&lineDoc.y { + if (doc2.title == 'rectangle' && typeof lineDoc.x === 'number' && typeof lineDoc.width === 'number' && typeof doc2.x === 'number' && typeof doc2.width === 'number'&& typeof doc2.y === 'number'&& typeof doc2.height === 'number'&& typeof lineDoc.y === 'number') { + if (lineDoc.x + lineDoc.width > doc2.x - (doc2.x - doc2.width) * 0.1 && lineDoc.x + lineDoc.width < doc2.x + doc2.width &&lineDoc.y>doc2.y&&lineDoc.y' + doc2.title + Math.floor(doc2.x).toString() + ';'; + const indexToRemove = docArray.findIndex(doc => doc === lineDoc); + if (indexToRemove !== -1) { + docArray.splice(indexToRemove, 1); + } + } + } + }); + } + } + }); + } + this.chartContent = mermaidCode; + }); } - return this.Document; - }; - docStyleProvider = (doc: Opt, props: Opt, property: string): any => { - if (property === StyleProp.PointerEvents) return 'none'; - return this._props.styleProvider?.(doc, props, property); }; - _closeRef = React.createRef(); render() { + console.log(this.chartContent) return (
- B; - B-->C; - B-->D[hi]; - `}/> +
); } -- cgit v1.2.3-70-g09d2 From 2ca4e70cd030fb9199d149060adf1b1d7c07857c Mon Sep 17 00:00:00 2001 From: Zachary Zhang Date: Tue, 12 Mar 2024 16:40:40 -0400 Subject: fix --- src/client/documents/Documents.ts | 1 + src/client/util/CurrentUserUtils.ts | 2 ++ src/client/views/nodes/DiagramBox.scss | 3 +++ src/client/views/nodes/DiagramBox.tsx | 2 +- 4 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 src/client/views/nodes/DiagramBox.scss (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 2f177b62a..96f4e4597 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -276,6 +276,7 @@ export class DocumentOptions { layout_hideDecorationTitle?: BOOLt = new BoolInfo('whether to suppress the document decortations title when selected'); _layout_hideContextMenu?: BOOLt = new BoolInfo('whether the context menu can be shown'); layout_diagramEditor?: STRt = new StrInfo("specify the JSX string for a diagram editor view") + layout_hideContextMenu?: BOOLt = new BoolInfo('whether the context menu can be shown'); layout_borderRounding?: string; _layout_modificationDate?: DATEt = new DateInfo('last modification date of doc layout', false); _layout_nativeDimEditable?: BOOLt = new BoolInfo('native dimensions can be modified using document decoration reizers', false); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index a41fd8796..18b37ffd1 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -33,6 +33,8 @@ import { ColorScheme } from "./SettingsManager"; import { SnappingManager } from "./SnappingManager"; import { UndoManager } from "./UndoManager"; import { CollectionView } from "../views/collections/CollectionView"; +import { LabelBox } from "../views/nodes/LabelBox"; +import { ImageBox } from "../views/nodes/ImageBox"; interface Button { // DocumentOptions fields a button can set diff --git a/src/client/views/nodes/DiagramBox.scss b/src/client/views/nodes/DiagramBox.scss new file mode 100644 index 000000000..50d9a6573 --- /dev/null +++ b/src/client/views/nodes/DiagramBox.scss @@ -0,0 +1,3 @@ +.mermaid{ + width:100%; +} \ No newline at end of file diff --git a/src/client/views/nodes/DiagramBox.tsx b/src/client/views/nodes/DiagramBox.tsx index 9ff2d27d2..e162360f8 100644 --- a/src/client/views/nodes/DiagramBox.tsx +++ b/src/client/views/nodes/DiagramBox.tsx @@ -3,7 +3,7 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; import { StyleProp } from '../StyleProvider'; -import './ComparisonBox.scss'; +import './DiagramBox.scss'; import { FieldView, FieldViewProps } from './FieldView'; import { PinProps, PresBox } from './trails'; import mermaid from 'mermaid'; -- cgit v1.2.3-70-g09d2 From e95d25eb8159bb7c753fa27e74e9baa8d3bffea6 Mon Sep 17 00:00:00 2001 From: alyssaf16 Date: Sat, 30 Mar 2024 00:06:27 -0400 Subject: working on making flashcards from pdf with ai --- src/.DS_Store | Bin 10244 -> 10244 bytes src/client/apis/gpt/GPT.ts | 8 +- src/client/documents/Documents.ts | 4 +- src/client/util/CurrentUserUtils.ts | 5 +- .../views/collections/CollectionCarouselView.scss | 18 +- .../views/collections/CollectionCarouselView.tsx | 76 +++++++++ src/client/views/collections/CollectionView.tsx | 9 + src/client/views/nodes/ComparisonBox.tsx | 181 ++++++++++++++++----- src/client/views/pdf/AnchorMenu.tsx | 23 +++ src/client/views/pdf/GPTPopup/GPTPopup.tsx | 39 ++++- src/client/views/pdf/PDFViewer.tsx | 4 + 11 files changed, 315 insertions(+), 52 deletions(-) (limited to 'src/client/documents') diff --git a/src/.DS_Store b/src/.DS_Store index f8d745dbf..f1f08fbb5 100644 Binary files a/src/.DS_Store and b/src/.DS_Store differ diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts index cea862330..968b45273 100644 --- a/src/client/apis/gpt/GPT.ts +++ b/src/client/apis/gpt/GPT.ts @@ -4,6 +4,7 @@ enum GPTCallType { SUMMARY = 'summary', COMPLETION = 'completion', EDIT = 'edit', + FLASHCARD = 'flashcard', } type GPTCallOpts = { @@ -14,9 +15,10 @@ type GPTCallOpts = { }; const callTypeMap: { [type: string]: GPTCallOpts } = { - summary: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: 'Summarize this text in simpler terms: ' }, - edit: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: 'Reword this: ' }, - completion: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: '' }, + summary: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Summarize this text in simpler terms: ' }, + edit: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Reword this: ' }, + flashcard: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Make flashcards out of this text with questions on the front and answers on the back: ' }, + completion: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: '' }, }; /** diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 2d2d13519..bf3d14560 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1054,8 +1054,8 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.SCREENSHOT), '', options); } - export function ComparisonDocument(options: DocumentOptions = { title: 'Comparison Box' }) { - return InstanceFromProto(Prototypes.get(DocumentType.COMPARISON), undefined, options); + export function ComparisonDocument(text: string, options: DocumentOptions = { title: 'Comparison Box' }) { + return InstanceFromProto(Prototypes.get(DocumentType.COMPARISON), text, options); } export function AudioDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 958240f94..ab0315ba1 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -341,14 +341,15 @@ pie title Minerals in my tap water creator:(opts:DocumentOptions)=> any // how to create the empty thing if it doesn't exist }[] = [ {key: "Note", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _layout_autoHeight: true }}, - {key: "Flashcard", creator: Docs.Create.ComparisonDocument, opts: { _layout_isFlashcard: true, _width: 300, _height: 300 }}, + {key: "Flashcard", creator: opts => Docs.Create.ComparisonDocument("", opts), opts: { _layout_isFlashcard: true, _width: 300, _height: 300}}, + // {key: "Flashcard", creator: Docs.Create.ComparisonDocument("", opts), opts: { _layout_isFlashcard: true, _width: 300, _height: 300 }}, //{key: "Flashcard", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _layout_autoHeight: true, _layout_enableAltContentUI: true}}, {key: "Equation", creator: opts => Docs.Create.EquationDocument("",opts), opts: { _width: 300, _height: 35, }}, {key: "Noteboard", creator: opts => Docs.Create.NoteTakingDocument([], opts), opts: { _width: 250, _height: 200, _layout_fitWidth: true}}, {key: "Simulation", creator: opts => Docs.Create.SimulationDocument(opts), opts: { _width: 300, _height: 300, }}, {key: "Collection", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 150, _height: 100, _layout_fitWidth: true }}, {key: "Webpage", creator: opts => Docs.Create.WebDocument("",opts), opts: { _width: 400, _height: 512, _nativeWidth: 850, data_useCors: true, }}, - {key: "Comparison", creator: Docs.Create.ComparisonDocument, opts: { _width: 300, _height: 300 }}, + {key: "Comparison", creator: opts => Docs.Create.ComparisonDocument("",opts), opts: { _width: 300, _height: 300 }}, {key: "Audio", creator: opts => Docs.Create.AudioDocument(nullAudio, opts),opts: { _width: 200, _height: 100, }}, {key: "Map", creator: opts => Docs.Create.MapDocument([], opts), opts: { _width: 800, _height: 600, _layout_fitWidth: true, }}, {key: "Screengrab", creator: Docs.Create.ScreenshotDocument, opts: { _width: 400, _height: 200 }}, diff --git a/src/client/views/collections/CollectionCarouselView.scss b/src/client/views/collections/CollectionCarouselView.scss index 130b31325..f115bb40a 100644 --- a/src/client/views/collections/CollectionCarouselView.scss +++ b/src/client/views/collections/CollectionCarouselView.scss @@ -13,7 +13,10 @@ } } .carouselView-back, -.carouselView-fwd { +.carouselView-fwd, +.carouselView-star, +.carouselView-remove, +.carouselView-check { position: absolute; display: flex; top: 42.5%; @@ -34,6 +37,19 @@ .carouselView-back { left: 20; } +.carouselView-star { + top: 0; + right: 20; +} +.carouselView-remove { + top: 80%; + left: 52%; +} +.carouselView-check { + top: 80%; + right: 52%; +} + .carouselView-back:hover, .carouselView-fwd:hover { background: lightgray; diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index dae16bafb..7f5176123 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -36,11 +36,67 @@ export class CollectionCarouselView extends CollectionSubView() { advance = (e: React.MouseEvent) => { e.stopPropagation(); this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + 1) % this.childLayoutPairs.length; + var startInd = this.layoutDoc._carousel_index; + + // if the star filter is selected + if (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] == 'star') { + // go to a new index that is starred, skip the ones that aren't + while (!this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_star`] && (startInd + 1) % this.childLayoutPairs.length != this.layoutDoc._carousel_index) { + startInd = (startInd + 1) % this.childLayoutPairs.length; + } + this.layoutDoc._carousel_index = startInd; + } + + // if the practice filter is selected + if (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] == 'practice') { + // go to a new index that is missed, skip the ones that are correct + while (this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_missed`] == 'correct' && (startInd + 1) % this.childLayoutPairs.length != this.layoutDoc._carousel_index) { + startInd = (startInd + 1) % this.childLayoutPairs.length; + } + this.layoutDoc._carousel_index = startInd; + } }; goback = (e: React.MouseEvent) => { e.stopPropagation(); this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length; + + var startInd = this.layoutDoc._carousel_index; + + // if the star filter is selected + if (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] == 'star') { + // go to a new index that is starred, skip the ones that aren't + while (!this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_star`] && (startInd - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length != this.layoutDoc._carousel_index) { + startInd = (startInd - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length; + } + this.layoutDoc._carousel_index = startInd; + } + + // if the practice filter is selected + if (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] == 'practice') { + // go to a new index that is missed, skip the ones that are correct + while (this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_missed`] == 'correct' && (startInd - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length != this.layoutDoc._carousel_index) { + startInd = (startInd - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length; + } + this.layoutDoc._carousel_index = startInd; + } + }; + + star = (e: React.MouseEvent) => { + e.stopPropagation(); + // stars the document when the button is pressed + const curDoc = this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)]; + if (curDoc.layout[`${this.fieldKey}_star`] == undefined) curDoc.layout[`${this.fieldKey}_star`] = true; + else curDoc.layout[`${this.fieldKey}_star`] = !curDoc.layout[`${this.fieldKey}_star`]; + }; + + missed = (e: React.MouseEvent, val: string) => { + e.stopPropagation(); + const curDoc = this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)]; + curDoc.layout[`${this.fieldKey}_missed`] = val; + this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + 1) % this.childLayoutPairs.length; + this.advance; }; + captionStyleProvider = (doc: Doc | undefined, captionProps: Opt, property: string): any => { // first look for properties on the document in the carousel, then fallback to properties on the container const childValue = doc?.['caption-' + property] ? this._props.styleProvider?.(doc, captionProps, property) : undefined; @@ -106,6 +162,15 @@ export class CollectionCarouselView extends CollectionSubView() {
+
+ +
+
this.missed(e, 'missed')} style={{ visibility: this.layoutDoc[`_${this._props.fieldKey}_filterOp`] == 'practice' ? 'visible' : 'hidden' }}> + +
+
this.missed(e, 'correct')} style={{ visibility: this.layoutDoc[`_${this._props.fieldKey}_filterOp`] == 'practice' ? 'visible' : 'hidden' }}> + +
); } @@ -120,6 +185,17 @@ export class CollectionCarouselView extends CollectionSubView() { color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color), }}> {this.content} +

+ Recently missed! +

{this.Document._chromeHidden ? null : this.buttons} ); diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 18eb4dd1f..168176edf 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -176,6 +176,15 @@ export class CollectionView extends ViewBoxAnnotatableComponent (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] = 'all'), icon: 'eye-slash' }); + revealItems.push({ description: 'Star', event: () => (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] = 'star'), icon: 'hand-point-up' }); + revealItems.push({ description: 'Practice Mode', event: () => (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] = 'practice'), icon: 'rotate' }); + + //revealItems.push({ description: 'Bring to Front', event: () => SelectionManager.Views.forEach(dv => dv._props.bringToFront?.(dv.Document, false)), icon: 'arrow-up' }); + !revealOptions && cm.addItem({ description: 'Filter Flashcards', addDivider: false, noexpand: true, subitems: revealItems, icon: 'layer-group' }); + const options = cm.findByDescription('Options...'); const optionItems = options && 'subitems' in options ? options.subitems : []; !Doc.noviceMode ? optionItems.splice(0, 0, { description: `${this.Document.forceActive ? 'Select' : 'Force'} Contents Active`, event: () => (this.Document.forceActive = !this.Document.forceActive), icon: 'project-diagram' }) : null; diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 81f624b20..ce862cd22 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -4,13 +4,14 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { emptyFunction, returnFalse, returnNone, returnZero, setupMoveUpEvents } from '../../../Utils'; import { Doc, Opt } from '../../../fields/Doc'; -import { DocCast, NumCast, StrCast } from '../../../fields/Types'; +import { DocCast, NumCast, RTFCast, StrCast } from '../../../fields/Types'; import { DocUtils, Docs } from '../../documents/Documents'; import { DragManager, dropActionType } from '../../util/DragManager'; import { undoBatch } from '../../util/UndoManager'; import { SnappingManager } from '../../util/SnappingManager'; import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; import { StyleProp } from '../StyleProvider'; +import { Tooltip } from '@mui/material'; import { CSSTransition } from 'react-transition-group'; import './ComparisonBox.scss'; import { DocumentView } from './DocumentView'; @@ -18,6 +19,8 @@ import { FieldView, FieldViewProps } from './FieldView'; import { PinProps, PresBox } from './trails'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; import { RichTextField } from '../../../fields/RichTextField'; +import { GPTCallType, gptAPICall } from '../../apis/gpt/GPT'; +import { DocData } from '../../../fields/DocSymbols'; @observer export class ComparisonBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { @@ -88,7 +91,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() if (this._isAnyChildContentActive) return; this._animating = 'all 200ms'; // on click, animate slider movement to the targetWidth - this.layoutDoc[this.clipWidthKey] = (targetWidth * 100) / this._props.PanelWidth(); + // this.layoutDoc[this.clipWidthKey] = (targetWidth * 100) / this._props.PanelWidth(); + this.layoutDoc[this.clipHeightKey] = (targetWidth * 100) / this._props.PanelHeight(); setTimeout( action(() => (this._animating = '')), @@ -128,7 +132,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() clearDoc = (fieldKey: string) => { delete this.dataDoc[fieldKey]; this.dataDoc[fieldKey] = 'empty'; - // this.dataDoc[this.fieldKey + '_1'] = 'empty'; }; // clearDoc = (fieldKey: string) => delete this.dataDoc[fieldKey]; @@ -182,47 +185,72 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : undefined; }; + hoverFlip = (side: string | undefined) => { + if (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'hover') this.layoutDoc[`_${this._props.fieldKey}_usePath`] = side; + }; @computed get overlayAlternateIcon() { const usepath = this.layoutDoc[`_${this._props.fieldKey}_usePath`]; return ( -
- setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => { - console.log(this.layoutDoc[`_${this._props.fieldKey}_revealOp`]); - if (!this.layoutDoc[`_${this._props.fieldKey}_revealOp`] || this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'flip') { - this.flipFlashcard(); - } - }) - } - style={{ - //display: this._props.isContentActive() && !SnappingManager.IsDragging ? 'flex' : 'none', - background: usepath === 'alternate' ? 'white' : 'black', - color: usepath === 'alternate' ? 'black' : 'white', - }}> - -
+ flip}> +
+ setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => { + console.log(this.layoutDoc[`_${this._props.fieldKey}_revealOp`]); + if (!this.layoutDoc[`_${this._props.fieldKey}_revealOp`] || this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'flip') { + this.flipFlashcard(); + } + }) + } + style={{ + //display: this._props.isContentActive() && !SnappingManager.IsDragging ? 'flex' : 'none', + background: usepath === 'alternate' ? 'white' : 'black', + color: usepath === 'alternate' ? 'black' : 'white', + }}> + +
+
); } render() { const clearButton = (which: string) => { return ( -
this.closeDown(e, which)} // prevent triggering slider movement in registerSliding - > - -
+ remove}> +
this.closeDown(e, which)} // prevent triggering slider movement in registerSliding + > + +
+
); }; const displayDoc = (which: string) => { const whichDoc = DocCast(this.dataDoc[which]); const targetDoc = DocCast(whichDoc?.annotationOn, whichDoc); // if there is no Doc in the first comparison slot, but the comparison box's fieldKey slot has a RichTextField, then render a text box to show the contents of the document's field key slot - const layoutTemplateString = !targetDoc && which.endsWith('1') && this.Document[this.fieldKey] instanceof RichTextField ? FormattedTextBox.LayoutString(this.fieldKey) : undefined; + //const layoutTemplateString = !targetDoc && which.endsWith('1') && this.Document[this.fieldKey] instanceof RichTextField ? FormattedTextBox.LayoutString(this.fieldKey) : undefined; + const subjectText = RTFCast(this.Document[this.fieldKey])?.Text; + const layoutTemplateString = !targetDoc + ? which.endsWith('0') && subjectText !== undefined + ? FormattedTextBox.LayoutString(this.fieldKey) + : which.endsWith('1') && (this.Document[which] instanceof RichTextField || typeof this.Document[which] === 'string') + ? FormattedTextBox.LayoutString(which) + : undefined + : undefined; + + if (which.endsWith('1') && !layoutTemplateString && targetDoc) { + const queryText = RTFCast(this.Document[this.fieldKey + '_1'])?.Text; + console.log('QUER' + queryText); + if (queryText?.includes('--TEXT--') && subjectText) { + this.Document[DocData][this.fieldKey + '_1'] = ''; + gptAPICall(queryText?.replace('--TEXT--', subjectText), GPTCallType.COMPLETION).then(value => (this.Document[DocData][this.fieldKey + '_1'] = value.trim())); + } + } + return targetDoc || layoutTemplateString ? ( <> () moveDocument={which.endsWith('1') ? this.moveDoc1 : this.moveDoc2} removeDocument={which.endsWith('1') ? this.remDoc1 : this.remDoc2} NativeWidth={() => NumCast(this.layoutDoc.width, 200)} - NativeHeight={() => NumCast(this.layoutDoc.height, 200)} + NativeHeight={(): number => { + return NumCast(this.layoutDoc.height, 200); + }} isContentActive={emptyFunction} isDocumentActive={returnFalse} whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} @@ -251,6 +281,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() ); }; const displayBox = (which: string, index: number, cover: number) => { + // if (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'hide/reveal') this.layoutDoc[this.clipHeightKey] = 100; + // else this.layoutDoc.height = 300; return (
this.registerSliding(e, cover)} ref={ele => this.createDropTarget(ele, which, index)}> {displayDoc(which)} @@ -258,26 +290,89 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() ); }; + const displayBoxReveal = (which: string, which2: string, index: number, cover: number) => { + return ( +
+
this.registerSliding(e, cover)} + ref={ele => this.createDropTarget(ele, which, 0)}> + {displayDoc(which)} +
+
this.registerSliding(e, cover)} + ref={ele => this.createDropTarget(ele, which2, 1)}> + {displayDoc(which2)} +
+
+ ); + }; + if (this.Document._layout_isFlashcard) { const side = this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate' ? 1 : 0; - if (!(this.dataDoc[this.fieldKey + '_0'] || this.dataDoc[this.fieldKey + '_0'] == 'empty')) this.dataDoc[this.fieldKey + '_0'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc); + // add text box when first created + if (!(this.dataDoc[this.fieldKey + '_0'] || this.dataDoc[this.fieldKey + '_0'] == 'empty')) { + console.log('TEXT HERE' + this.dataDoc.data); + const newDoc = Docs.Create.TextDocument(StrCast(this.dataDoc.data)); + this.addDoc(newDoc, this.fieldKey + '_0'); + } + // if (!(this.dataDoc[this.fieldKey + '_0'] || this.dataDoc[this.fieldKey + '_0'] == 'empty')) this.dataDoc[this.fieldKey + '_0'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc); if (!(this.dataDoc[this.fieldKey + '_1'] || this.dataDoc[this.fieldKey + '_1'] == 'empty')) this.dataDoc[this.fieldKey + '_1'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc); - return ( -
{ - if (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'hover') this.layoutDoc[`_${this._props.fieldKey}_usePath`] = 'alternate'; - }} - onMouseLeave={() => { - if (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'hover') this.layoutDoc[`_${this._props.fieldKey}_usePath`] = undefined; - }}> - {displayBox(`${this.fieldKey}_${side}`, side, this._props.PanelWidth() - 3)} + if (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'hide/reveal') { + { + return ( +
+ {displayBox(`${this.fieldKey}_0`, side, this._props.PanelHeight() - 3)} + {displayBox(`${this.fieldKey}_1`, 1, this._props.PanelHeight() - 3)} +
+ ); + // return ( + //
+ // {displayBox(`${this.fieldKey}_2`, 1, this._props.PanelHeight() - 3)} + //
+ // {displayBox(`${this.fieldKey}_1`, 0, 0)} + //
+ //
+ // ); + // return ( + //
+ // {displayBox(`${this.fieldKey}_${side === 0 ? 1 : 0}`, side, this._props.PanelHeight() / 2)} + // {displayBox(`${this.fieldKey}_${side === 0 ? 0 : 1}`, 1, this._props.PanelHeight() / 2)} - {this.overlayAlternateIcon} -
- ); + //
+ // ); + } + } else { + return ( +
{ + this.hoverFlip('alternate'); + }} + onMouseLeave={() => { + this.hoverFlip(undefined); + }}> + {displayBox(`${this.fieldKey}_${side === 0 ? 1 : 0}`, side, this._props.PanelWidth() - 3)} + {/* {displayBoxReveal(`${this.fieldKey}_${side}`, side, this._props.PanelWidth() - 3)} + {displayBoxReveal(`${this.fieldKey}_${side === 0 ? 1 : 0}`, side === 0 ? 1 : 0, this._props.PanelWidth() - 3)} */} + {/*
{displayBoxReveal(`${this.fieldKey}_${side}`, side, this._props.PanelWidth() - 3)}
*/} + {/*
+ {displayBox(`${this.fieldKey}_${side === 0 ? 1 : 0}`, side === 0 ? 1 : 0, this._props.PanelWidth() - 3)} +
*/} + + {this.overlayAlternateIcon} +
+ ); + } } else { return (
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index d0688c338..844c1e36d 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -6,6 +6,7 @@ import * as React from 'react'; import { ColorResult } from 'react-color'; import { Utils, returnFalse, setupMoveUpEvents, unimplementedFunction } from '../../../Utils'; import { Doc, Opt } from '../../../fields/Doc'; +import { DocUtils, Docs } from '../../documents/Documents'; import { GPTCallType, gptAPICall } from '../../apis/gpt/GPT'; import { DocumentType } from '../../documents/DocumentTypes'; import { SelectionManager } from '../../util/SelectionManager'; @@ -87,6 +88,22 @@ export class AnchorMenu extends AntimodeMenu { GPTPopup.Instance.setLoading(false); }; + gptFlashcards = async (e: React.PointerEvent) => { + // move this logic to gptpopup, need to implement generate again + // GPTPopup.Instance.setVisible(true); + // GPTPopup.Instance.setMode(GPTPopupMode.FLASHCARD); + // GPTPopup.Instance.setLoading(true); + + try { + const res = await gptAPICall(this.selectedText, GPTCallType.FLASHCARD); + GPTPopup.Instance.setText(res || 'Something went wrong.'); + GPTPopup.Instance.transferToFlashcard(); + } catch (err) { + console.error(err); + } + GPTPopup.Instance.setLoading(false); + }; + pointerDown = (e: React.PointerEvent) => { setupMoveUpEvents( this, @@ -176,6 +193,12 @@ export class AnchorMenu extends AntimodeMenu { color={SettingsManager.userColor} /> )} + } + color={SettingsManager.userColor} + /> {AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : ( { } }; + transferToFlashcard = () => { + const senArr = this.text.split('.'); + for (var i = 0; i < senArr.length; i += 2) { + console.log('SEN: ' + senArr[i]); + const newDoc = Docs.Create.ComparisonDocument(senArr[i], { _layout_isFlashcard: true, _width: 300, _height: 300 }); + newDoc.text = senArr[i]; + this.addDoc(newDoc, this.sidebarId); + const anchor = AnchorMenu.Instance?.GetAnchor(undefined, false); + if (anchor) { + DocUtils.MakeLink(newDoc, anchor, { + link_relationship: 'GPT Summary', + }); + } + } + }; + /** * Transfers the image urls to actual image docs */ @@ -213,6 +232,23 @@ export class GPTPopup extends ObservableReactComponent { ); }; + flashcardBox = () => { + // const textArr = this.text.split("."); + // textArr.forEach(function(sentence) { + // console.log(sentence); + + // }); + // const newDoc = Docs.Create.ComparisonDocument(); + // this.addToCollection?.(newDoc); + // // const newDoc = Docs.Create.ComparisonDocument(); + // DocUtils.copyDragFactory(Doc.UserDoc().emptyFlashcard as Doc); + // // this.addToCollection?.(newDoc); + // // return newDoc; + // + const newDoc = Docs.Create.TextDocument('Hello there'); + this.addDoc?.(newDoc); + }; + data = () => { return (
@@ -268,6 +304,7 @@ export class GPTPopup extends ObservableReactComponent { <> } color={StrCast(Doc.UserDoc().userVariantColor)} /> ); + } + + const totalWidth = amButtons * 35 + (amButtons * 2* 5) + 6; + return ( +
+ {buttons} +
+ ); + } + + + + + + + + + + + // @computed get content() { @@ -450,7 +533,7 @@ export class CollectionCardView extends CollectionSubView() { let translate = 0 - if (this.inactiveDocs().length != this.myChildLayoutPairs.length) { + if (this.inactiveDocs().length != this.myChildLayoutPairs.length && this.inactiveDocs().length < 10) { translate += this.panelWidth() / 2; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 2d05b5490..11193f496 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -2010,4 +2010,4 @@ ScriptingGlobals.add(function datavizFromSchema(doc: Doc) { SchemaCSVPopUp.Instance.setVisible(true); } }); -}); +}); \ No newline at end of file diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 526315802..9056917fe 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -105,10 +105,12 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) { selected ? selected.CollectionFreeFormDocumentView?.float() : console.log('[FontIconBox.tsx] toggleOverlay failed'); }); -ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links', checkResult?: boolean, persist?: boolean) { + + +ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'custom' | 'create', checkResult?: boolean, persist?: boolean, customNumber?: number) { const selected = SelectionManager.Docs.lastElement(); // prettier-ignore - const map: Map<'flashcards' | 'center' |'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([ + const map: Map<'flashcards' | 'center' |'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'custom' | 'create', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([ ['grid', { checkResult: (doc:Doc) => BoolCast(doc?._freeform_backgroundGrid, false), setDoc: (doc:Doc,dv:DocumentView) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid, @@ -156,6 +158,21 @@ ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "links", }], + ['custom', { + checkResult: (doc:Doc) => StrCast(doc?.cardSort), + setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "custom", + }], + + //in an ideal world lol + + + // ['create', { + // checkResult: (doc:Doc) => NumCast(doc?.customSortCount), + // setDoc: (doc:Doc,dv:DocumentView) => doc.customSortCount = NumCast(doc.customSortCount) + 1, + // }], + + + ]); if (checkResult) { -- cgit v1.2.3-70-g09d2 From 3c5b2353cb20843e968e51fdff58cc92f101ed51 Mon Sep 17 00:00:00 2001 From: aidahosa1 Date: Tue, 14 May 2024 02:14:58 -0400 Subject: getting close to done --- src/client/documents/Documents.ts | 3 +- src/client/util/CurrentUserUtils.ts | 6 ++- src/client/views/MainView.tsx | 4 +- .../views/collections/CollectionCardDeckView.scss | 2 +- .../views/collections/CollectionCardDeckView.tsx | 63 +++++++++++++++++----- .../CollectionFreeFormLayoutEngines.tsx | 26 ++++----- .../collections/collectionFreeForm/MarqueeView.tsx | 4 +- src/client/views/global/globalScripts.ts | 35 +++++++----- 8 files changed, 94 insertions(+), 49 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index df69e215a..57f91399a 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -500,7 +500,8 @@ export class DocumentOptions { userColor?: STRt = new StrInfo('color associated with a Dash user (seen in header fields of shared documents)'); cardSort?: STRt = new StrInfo('way cards are sorted in deck view'); - customSortCount?: NUMt = new NumInfo('number of custom sorts the user has created'); + customSortNumber?: NUMt = new NumInfo('number of custom sorts the user has created'); + customHashMap?: List // card_sort_time?: BOOLt = new BoolInfo('whether sorting cards in deck view by time'); // card_sort_type?: BOOLt = new BoolInfo('whether sorting cards in deck view by type'); // card_sort_color?: BOOLt = new BoolInfo('whether sorting cards in deck view by color'); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 59560dd78..abc728cd3 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -730,7 +730,11 @@ pie title Minerals in my tap water { title: "Type", icon:"eye", toolTip:"Sort by document type", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"docType", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, { title: "Color", icon:"palette", toolTip:"Sort by document color", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"color", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, { title: "Links", icon:"link", toolTip:"Sort by its links", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"links", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, - { title: "Create", icon:"plus", toolTip:"Create new custom groupings!", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"custom", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + { title: "Create", icon:"robot", toolTip:"Create your first custom grouping!", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"custom1", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + { title: "Create", icon:"star", toolTip:"Create your second custom grouping!", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"custom2", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + { title: "Create", icon:"satellite", toolTip:"Create your third custom grouping!", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"custom3", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + + // ...customs ] diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index a5882dcf3..f1296e46a 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -527,7 +527,9 @@ export class MainView extends ObservableReactComponent<{}> { fa.faArrowsDownToLine, fa.faPalette, fa.faHourglassHalf, - fa.faRobot + fa.faRobot, + fa.faSatellite, + fa.faStar ] ); } diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss index 09d6f70ea..babc604b5 100644 --- a/src/client/views/collections/CollectionCardDeckView.scss +++ b/src/client/views/collections/CollectionCardDeckView.scss @@ -68,7 +68,7 @@ button { .card-item-active, .card-item { position: relative; - transition: transform 0.3s ease-in-out; + transition: transform 0.5s ease-in-out; } .card-item-active { diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index 996c5711b..fc183b72b 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -2,7 +2,7 @@ import { ObservableMap, action, computed, makeObservable, observable } from 'mob import { observer } from 'mobx-react'; import * as React from 'react'; import { Utils, returnFalse, returnTrue, returnZero } from '../../../Utils'; -import { Doc, DocListCast, Field } from '../../../fields/Doc'; +import { Doc, DocListCast, Field, StrListCast } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { NumCast, ScriptCast, StrCast, BoolCast, DocCast } from '../../../fields/Types'; import { DragManager } from '../../util/DragManager'; @@ -17,6 +17,7 @@ import { LinkManager } from '../../util/LinkManager'; import { DocumentType } from '../../documents/DocumentTypes'; import { forEach } from 'lodash'; import { SnappingManager } from '../../util/SnappingManager'; +import { List } from '../../../fields/List'; @observer export class CollectionCardView extends CollectionSubView() { @@ -70,11 +71,49 @@ export class CollectionCardView extends CollectionSubView() { super(props); makeObservable(this); // this.rotationDegree(7); + + if (this._props.Document.customHashMap != undefined){ + this.customGroupDictionary = this.getCustoms(StrListCast(this._props.Document.customHashMap)) + } + } + + @observable customGroupDictionary: Map[] = [new Map(), new Map(), new Map()]; + + @computed get mapToField(): List { + const resultList = new List(); // Creating a new ListImpl instance for strings + + this.customGroupDictionary.forEach(map => { + // Convert each map to a single string with entries formatted as "key:value" + const mapString = Array.from(map.entries()) + .map(([key, value]) => `${key}:${value}`) + .join(','); // Join all key-value pairs with commas + resultList.push(mapString); // Add the formatted string of the current map to the list + }); + + return resultList; + } + + getCustoms = (stringFrom: string[]): Map[] => { + return stringFrom.map(s => { + // Create a new Map object for each string. + const map = new Map(); + // Split the string by commas to get key-value pairs, then process each pair. + s.split(',').forEach(pair => { + const [key, value] = pair.split(':'); + map.set(Number(key), Number(value)); + }); + return map; + }); } + + + + private _dropDisposer?: DragManager.DragDropDisposer; componentWillUnmount() { + console.log("hey hey hey") this._dropDisposer?.(); } @@ -253,9 +292,11 @@ export class CollectionCardView extends CollectionSubView() { break; case 'custom': - typeA = this.customGroupDictionary.get(docs.indexOf(docA)) ?? ''; + + console.log(this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)]) + typeA = this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].get(docs.indexOf(docA)) ?? '9999'; // console.log(typeA + "A") - typeB = this.customGroupDictionary.get(docs.indexOf(docB)) ?? ''; + typeB = this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].get(docs.indexOf(docB)) ?? '9999'; // console.log(typeB + 'b') break; @@ -397,7 +438,7 @@ export class CollectionCardView extends CollectionSubView() { SnappingManager.SetIsResizing(undefined); this._forceChildXf = !this._forceChildXf; }), - 400 + 700 ); }} style={{ @@ -419,11 +460,13 @@ export class CollectionCardView extends CollectionSubView() { }); } - @observable amGroups = 0; - @observable customGroupDictionary = new Map(); + + + @action toggleButton(childPairIndex: number, buttonID: number) { - this.customGroupDictionary.set(childPairIndex, buttonID); + this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].set(childPairIndex, buttonID); + this._props.Document.customHashMap = this.mapToField } renderButtons(childPairIndex: number) { @@ -431,11 +474,7 @@ export class CollectionCardView extends CollectionSubView() { let amButtons = 4; - if (this.amGroups > 4) { - amButtons = this.amGroups; - } - - let activeButtonIndex = this.customGroupDictionary.get(childPairIndex); + let activeButtonIndex = this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].get(childPairIndex); for (let i = 0; i < amButtons; i++) { const isActive = activeButtonIndex == i; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index becad63f6..22005eb23 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -126,27 +126,20 @@ export function computeStarburstLayout(poolData: Map, pivotDoc } // export function computeCardDeckLayout(poolData: Map, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) { -// console.log('hi'); // const docMap = new Map(); -// const spreadWidth = Math.min(panelDim[0], childPairs.length * 50); // Total width of the spread -// const startX = -(spreadWidth / 2); // Starting X position -// const fanAngle = 5; // Angle in degrees for fanning out cards -// const baseZIndex = 1000; // Base Z-index to ensure cards are stacked in order +// const burstDiam = [NumCast(pivotDoc._width), NumCast(pivotDoc._height)]; +// const burstScale = NumCast(pivotDoc._starburstDocScale, 1); // childPairs.forEach(({ layout, data }, i) => { // const aspect = NumCast(layout._height) / NumCast(layout._width); -// const docSize = Math.min(400, NumCast(layout._width)) * NumCast(pivotDoc._starburstDocScale, 1); -// const posX = startX + (spreadWidth / childPairs.length) * i; -// const posY = 0; // Adjust if you want to change the vertical alignment -// const rotation = (i - (childPairs.length / 2)) * fanAngle; // Calculate rotation for fanning effect - +// const docSize = Math.min(Math.min(400, NumCast(layout._width)), Math.min(400, NumCast(layout._width)) / aspect) * burstScale; +// const deg = (i / childPairs.length) * Math.PI * 2; // docMap.set(layout[Id], { -// x: posX, -// y: posY, +// 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: baseZIndex + i, -// rotation: rotation, +// zIndex: NumCast(layout.zIndex), // pair: { layout, data }, // replica: '', // color: 'white', @@ -154,9 +147,8 @@ export function computeStarburstLayout(poolData: Map, pivotDoc // transition: 'all 0.3s', // }); // }); - -// const divider = { type: 'div', color: 'transparent', x: -panelDim[0] / 2, y: -panelDim[1] / 2, width: 15, height: 15, payload: undefined }; -// return normalizeResults(panelDim, 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, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) { diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 98684ae98..7e7a5c2c6 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -533,7 +533,7 @@ export class MarqueeView extends ObservableReactComponent any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([ + const map: Map<'flashcards' | 'center' |'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'custom1' | 'custom2' | 'custom3', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([ ['grid', { checkResult: (doc:Doc) => BoolCast(doc?._freeform_backgroundGrid, false), setDoc: (doc:Doc,dv:DocumentView) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid, @@ -158,21 +158,28 @@ ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "links", }], - ['custom', { + ['custom1', { checkResult: (doc:Doc) => StrCast(doc?.cardSort), - setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "custom", - }], - - //in an ideal world lol - - - // ['create', { - // checkResult: (doc:Doc) => NumCast(doc?.customSortCount), - // setDoc: (doc:Doc,dv:DocumentView) => doc.customSortCount = NumCast(doc.customSortCount) + 1, - // }], + setDoc: (doc, dv) => { + doc.cardSort = "custom"; + doc.customSortNumber = 0; + } + }], - + ['custom2', { + checkResult: (doc:Doc) => StrCast(doc?.cardSort), + setDoc: (doc, dv) => { + doc.cardSort = "custom"; + doc.customSortNumber = 1; + console.log(doc.customSortNumber + " numberrrrrrrr") + } }], + ['custom3', { + checkResult: (doc:Doc) => StrCast(doc?.cardSort), + setDoc: (doc, dv) => { + doc.cardSort = "custom"; + doc.customSortNumber = 2; + } }], ]); if (checkResult) { -- cgit v1.2.3-70-g09d2 From 69e286b504c2f1bbef7d489dc675b9e54ef8d983 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 16 May 2024 19:52:13 -0400 Subject: refactor/cleanup of pres animations. changed effects to start halfway through a zoom transition. --- src/client/apis/gpt/customization.ts | 18 +- src/client/documents/Documents.ts | 1 + src/client/util/DocumentManager.ts | 6 +- src/client/views/PinFuncs.ts | 2 +- src/client/views/PropertiesView.tsx | 2 +- src/client/views/animationtimeline/Timeline.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 18 +- src/client/views/nodes/trails/PresBox.tsx | 50 +-- src/client/views/nodes/trails/PresEnums.ts | 2 +- src/client/views/nodes/trails/SlideEffect.scss | 2 +- src/client/views/nodes/trails/SlideEffect.tsx | 402 +++++------------------- src/client/views/nodes/trails/SpringUtils.ts | 2 +- 12 files changed, 134 insertions(+), 373 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/apis/gpt/customization.ts b/src/client/apis/gpt/customization.ts index 61b666bd3..2262886a2 100644 --- a/src/client/apis/gpt/customization.ts +++ b/src/client/apis/gpt/customization.ts @@ -25,13 +25,13 @@ export const addCustomizationProperty = (type: CustomizationType, name: string, // includes most fields but is not yet fully comprehensive export const gptSlideProperties = [ 'title', + 'config_zoom', 'presentation_transition', - 'presEaseFunc', + 'presentation_easeFunc', 'presentation_effect', 'presentation_effectDirection', - 'presEffectTiming', - 'config_zoom', - 'presPlayAudio', + 'presentation_effectTiming', + 'presentation_playAudio', 'presentation_zoomText', 'presentation_hideBefore', 'presentation_hide', @@ -43,20 +43,20 @@ export const gptSlideProperties = [ const setupPresSlideCustomization = () => { addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'title', 'is the title/name of the slide.'); addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_transition', 'is a number in milliseconds for how long it should take to transition/move to a slide.'); - addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presEaseFunc', 'is the easing function for the movement to the slide.', ['Ease', 'Ease In', 'Ease Out', 'Ease Out', 'Ease In Out', 'Linear']); + addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_easeFunc', 'is the easing function for the movement to the slide.', ['Ease', 'Ease In', 'Ease Out', 'Ease Out', 'Ease In Out', 'Linear']); - addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_effect', 'is an effect applied to the slide when we transition to it.', ['None', 'Zoom', 'Fade in', 'Bounce', 'Flip', 'Rotate', 'Roll']); + addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_effect', 'is an effect applied to the slide when we transition to it.', ['None', 'Expand', 'Fade in', 'Bounce', 'Flip', 'Rotate', 'Roll']); addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_effectDirection', 'is what direction the effect is applied.', ['Enter from left', 'Enter from right', 'Enter from bottom', 'Enter from Top', 'Enter from center']); addCustomizationProperty( CustomizationType.PRES_TRAIL_SLIDE, - 'presEffectTiming', + 'presentation_effectTiming', "is a json object of the format: {type: string, stiffness: number, damping: number, mass: number}. Type is always “custom”. Controls the spring-based timing of the presentation effect animation. Stiffness, damping, and mass control the physics-based properties of spring animations. This is used to create a more natural looking timing, bouncy effects, etc. Use spring physics to adjust these parameters to match the user's description of how they want to animate the effect." ); addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'config_zoom', 'is a number from 0 to 1.0 indicating the percentage we should zoom into the slide.'); // boolean values - addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presPlayAudio', 'is a boolean value indicating if we should play audio when we go to the slide.'); + addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_playAudio', 'is a boolean value indicating if we should play audio when we go to the slide.'); addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_zoomText', 'is a boolean value indicating if we should zoom into text selections when we go to the slide.'); addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_hideBefore', 'is a boolean value indicating if we should hide the slide before going to it.'); addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_hide', 'is a boolean value indicating if we should hide the slide during the presentation.'); @@ -82,7 +82,7 @@ export const getSlideTransitionSuggestions = async (inputText: string) => { */ const prompt = - "I want to generate four distinct types of slide effect animations. Return a json of the form {effect: string, direction: string, stiffness: number, damping: number, mass: number}[] with four elements. Effect is the type of animation; its only possible values are ['Zoom', 'Fade in', 'Bounce', 'Flip', 'Rotate', 'Roll']. Direction is the direction that the animation starts from; its only possible values are ['Enter from left', 'Enter from right', 'Enter from bottom', 'Enter from Top', 'Enter from center']. Stiffness, damping, and mass control the physics-based properties of spring animations. This is used to create a more natural-looking timing, bouncy effects, etc. Use spring physics to adjust these parameters to animate the effect."; + "I want to generate four distinct types of slide effect animations. Return a json of the form {effect: string, direction: string, stiffness: number, damping: number, mass: number}[] with four elements. Effect is the type of animation; its only possible values are ['Expand', 'Fade in', 'Bounce', 'Flip', 'Rotate', 'Roll']. Direction is the direction that the animation starts from; its only possible values are ['Enter from left', 'Enter from right', 'Enter from bottom', 'Enter from Top', 'Enter from center']. Stiffness, damping, and mass control the physics-based properties of spring animations. This is used to create a more natural-looking timing, bouncy effects, etc. Use spring physics to adjust these parameters to animate the effect."; const customInput = inputText ?? 'Make them as contrasting as possible with different effects and timings ranging from gentle to energetic.'; diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 1c18c0d84..24dd5cf37 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -343,6 +343,7 @@ export class DocumentOptions { config_map?: STRt = new StrInfo('text location of map', false); config_panX?: NUMt = new NumInfo('panX saved as a view spec', false); config_panY?: NUMt = new NumInfo('panY saved as a view spec', false); + config_zoom?: NUMt = new NumInfo('zoom saved as a view spec', false); config_viewScale?: NUMt = new NumInfo('viewScale saved as a view Spec', false); presentation_transition?: NUMt = new NumInfo('the time taken for the transition TO a document', false); presentation_duration?: NUMt = new NumInfo('the duration of the slide in presentation view', false); diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 09a8194ca..8ad6ddf47 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -338,11 +338,13 @@ export class DocumentManager { // viewSpec !== docView.Document && docView.ComponentView?.focus?.(viewSpec, options); PresBox.restoreTargetDocView(docView, viewSpec, options.zoomTime ?? 500); - Doc.linkFollowHighlight(viewSpec ? [docView.Document, viewSpec] : docView.Document, undefined, options.effect); + // if there's an options.effect, it will be handled from linkFollowHighlight. We delay the start of + // the highlight so that the target document can be somewhat centered so that the effect/highlight will be seen + // bcz: should this delay be an options parameter? + setTimeout(() => Doc.linkFollowHighlight(viewSpec ? [docView.Document, viewSpec] : docView.Document, undefined, options.effect), (options.zoomTime ?? 0) * 0.5); if (options.playMedia) docView.ComponentView?.playFrom?.(NumCast(docView.Document._layout_currentTimecode)); if (options.playAudio) DocumentManager.playAudioAnno(docView.Document); if (options.toggleTarget && (!options.didMove || docView.Document.hidden)) docView.Document.hidden = !docView.Document.hidden; - if (options.effect) docView.Document[Animation] = options.effect; if (options.zoomTextSelections && Doc.IsUnhighlightTimerSet() && contextView && targetDoc.text_html) { // if the docView is a text anchor, the contextView is the PDF/Web/Text doc diff --git a/src/client/views/PinFuncs.ts b/src/client/views/PinFuncs.ts index 3d998ecaf..430455644 100644 --- a/src/client/views/PinFuncs.ts +++ b/src/client/views/PinFuncs.ts @@ -54,7 +54,7 @@ export function PinDocView(pinDocIn: Doc, pinProps: PinProps, targetDoc: Doc) { pinDoc.config_width = NumCast(targetDoc.width); pinDoc.config_height = NumCast(targetDoc.height); } - if (pinProps.pinAudioPlay) pinDoc.presPlayAudio = true; + if (pinProps.pinAudioPlay) pinDoc.presentation_playAudio = true; if (pinProps.pinData) { pinDoc.config_pinData = pinProps.pinData.scrollable || diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index df4ed98ac..024db82a4 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -1487,7 +1487,7 @@ export class PropertiesView extends ObservableReactComponent this.changeAnimationBehavior(e.currentTarget.value)} value={StrCast(this.sourceAnchor?.followLinkAnimEffect, 'default')}> - {[PresEffect.None, PresEffect.Zoom, PresEffect.Lightspeed, PresEffect.Fade, PresEffect.Flip, PresEffect.Rotate, PresEffect.Bounce, PresEffect.Roll].map(effect => ( + {[PresEffect.None, PresEffect.Expand, PresEffect.Lightspeed, PresEffect.Fade, PresEffect.Flip, PresEffect.Rotate, PresEffect.Bounce, PresEffect.Roll].map(effect => ( diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx index c4d35330b..d9ff21035 100644 --- a/src/client/views/animationtimeline/Timeline.tsx +++ b/src/client/views/animationtimeline/Timeline.tsx @@ -547,7 +547,7 @@ export class Timeline extends ObservableReactComponent {
{this.drawTicks()}
-
+
{[...this.children, this._props.Document].map(doc => ( diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 6f9e14c1b..c59cd0ee4 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -934,7 +934,7 @@ export class DocumentViewInternal extends DocComponent - {this._componentView?.isUnstyledView?.() ? renderDoc : DocumentViewInternal.AnimationEffect(renderDoc, this.Document[Animation], this.Document)} + {this._componentView?.isUnstyledView?.() || this.Document.type === DocumentType.CONFIG ? renderDoc : DocumentViewInternal.AnimationEffect(renderDoc, this.Document[Animation], this.Document)} {borderPath?.jsx}
); @@ -958,7 +958,7 @@ export class DocumentViewInternal extends DocComponent{renderDoc} - // case PresEffect.Fade: return {renderDoc} + case PresEffect.Expand: return {renderDoc} + case PresEffect.Flip: return {renderDoc} + case PresEffect.Rotate: return {renderDoc} + case PresEffect.Bounce: return {renderDoc} + case PresEffect.Roll: return {renderDoc} + // case PresEffect.Fade: return {renderDoc} case PresEffect.Fade: return {renderDoc} - case PresEffect.Flip: return {renderDoc} - case PresEffect.Rotate: return {renderDoc} - case PresEffect.Bounce: return {renderDoc} - case PresEffect.Roll: return {renderDoc} // keep as preset, doesn't really make sense with spring config case PresEffect.Lightspeed: return {renderDoc}; case PresEffect.None: @@ -1416,7 +1416,7 @@ export class DocumentView extends DocComponent() {
console.log('PARSE error', e)} renderInWrapper={false} jsx={StrCast(this._htmlOverlayText)} />
, - { ...(this._htmlOverlayEffect ?? {}), presentation_effect: effect ?? PresEffect.Zoom } as any as Doc, + { ...(this._htmlOverlayEffect ?? {}), presentation_effect: effect ?? PresEffect.Expand } as any as Doc, this.Document )}
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 69d03ac2e..101f28ae7 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -185,7 +185,7 @@ export class PresBox extends ViewBoxBaseComponent() { @computed get currCPoints() { - const strPoints = this.activeItem.presEaseFunc ? StrCast(this.activeItem.presEaseFunc) : 'ease'; + const strPoints = this.activeItem.presentation_easeFunc ? StrCast(this.activeItem.presentation_easeFunc) : 'ease'; return EaseFuncToPoints(strPoints); } @@ -858,9 +858,9 @@ export class PresBox extends ViewBoxBaseComponent() { effect: activeItem, noSelect: true, openLocation: targetDoc.type === DocumentType.PRES ? ((OpenWhere.replace + ':' + PresBox.PanelName) as OpenWhere) : OpenWhere.addLeft, - easeFunc: StrCast(activeItem.presEaseFunc, 'ease') as any, + easeFunc: StrCast(activeItem.presentation_easeFunc, 'ease') as any, zoomTextSelections: BoolCast(activeItem.presentation_zoomText), - playAudio: BoolCast(activeItem.presPlayAudio), + playAudio: BoolCast(activeItem.presentation_playAudio), playMedia: activeItem.presentation_mediaStart === 'auto', }; if (activeItem.presentation_openInLightbox) { @@ -1575,16 +1575,16 @@ export class PresBox extends ViewBoxBaseComponent() { @undoBatch updateEaseFunc = (activeItem: Doc) => { - activeItem.presEaseFunc = activeItem.presEaseFunc === 'linear' ? 'ease' : 'linear'; + activeItem.presentation_easeFunc = activeItem.presentation_easeFunc === 'linear' ? 'ease' : 'linear'; this.selectedArray.forEach(doc => { - doc.presEaseFunc = activeItem.presEaseFunc; + doc.presentation_easeFunc = activeItem.presentation_easeFunc; }); }; setEaseFunc = (activeItem: Doc, easeFunc: string) => { - activeItem.presEaseFunc = easeFunc; + activeItem.presentation_easeFunc = easeFunc; this.selectedArray.forEach(doc => { - doc.presEaseFunc = activeItem.presEaseFunc; + doc.presentation_easeFunc = activeItem.presentation_easeFunc; }); }; @@ -1602,9 +1602,9 @@ export class PresBox extends ViewBoxBaseComponent() { @undoBatch updateEffectTiming = (activeItem: Doc, timing: SpringSettings) => { - activeItem.presEffectTiming = JSON.stringify(timing); + activeItem.presentation_effectTiming = JSON.stringify(timing); this.selectedArray.forEach(doc => { - doc.presEffectTiming = activeItem.presEffectTiming; + doc.presentation_effectTiming = activeItem.presentation_effectTiming; }); }; @@ -1829,7 +1829,7 @@ export class PresBox extends ViewBoxBaseComponent() { @computed get transitionDropdown() { const { activeItem } = this; // Retrieving spring timing properties - const timing = StrCast(activeItem.presEffectTiming); + const timing = StrCast(activeItem.presentation_effectTiming); let timingConfig: SpringSettings | undefined; if (timing) { timingConfig = JSON.parse(timing); @@ -1847,8 +1847,8 @@ export class PresBox extends ViewBoxBaseComponent() { if (activeItem && this.targetDoc) { const transitionSpeed = activeItem.presentation_transition ? NumCast(activeItem.presentation_transition) / 1000 : 0.5; const zoom = NumCast(activeItem.config_zoom, 1) * 100; - const effect = StrCast(activeItem.presentation_effect) ? StrCast(activeItem.presentation_effect) : PresEffect.None; - const direction = StrCast(activeItem.presentation_effectDirection); + const effect = StrCast(activeItem.presentation_effect) ? (StrCast(activeItem.presentation_effect) as any as PresEffect) : PresEffect.None; + const direction = StrCast(activeItem.presentation_effectDirection) as PresEffectDirection; return ( <> @@ -1876,7 +1876,7 @@ export class PresBox extends ViewBoxBaseComponent() { /> } onClick={() => { @@ -1894,7 +1894,7 @@ export class PresBox extends ViewBoxBaseComponent() { type={Type.TERT} icon={this.isLoading ? : } iconPlacement="right" - color={StrCast(Doc.UserDoc().userVariantColor)} + color={SnappingManager.userVariantColor} onClick={() => { this.stopDictation(); this.customizeWithGPT(this.chatInput); @@ -1919,7 +1919,7 @@ export class PresBox extends ViewBoxBaseComponent() { > Movement () {
{/* Easing function */} { if (typeof val === 'string') { if (val !== 'custom') { @@ -2017,7 +2017,7 @@ export class PresBox extends ViewBoxBaseComponent() { type={Type.TERT} icon={this.isLoading ? : } iconPlacement="right" - color={StrCast(Doc.UserDoc().userVariantColor)} + color={SnappingManager.userVariantColor} onClick={this.customizeAnimations} />
@@ -2053,7 +2053,7 @@ export class PresBox extends ViewBoxBaseComponent() { mass: elem.mass, }); }}> - +
@@ -2062,7 +2062,7 @@ export class PresBox extends ViewBoxBaseComponent() {
{/* Effect dropdown */} () { {effect !== PresEffect.Lightspeed && ( <> () { Preview Effect
- +
@@ -2224,9 +2224,9 @@ export class PresBox extends ViewBoxBaseComponent() { { - activeItem.presPlayAudio = !BoolCast(activeItem.presPlayAudio); + activeItem.presentation_playAudio = !BoolCast(activeItem.presentation_playAudio); }} color={SnappingManager.userColor} /> @@ -2239,7 +2239,7 @@ export class PresBox extends ViewBoxBaseComponent() { }} color={SnappingManager.userColor} /> -
diff --git a/src/client/views/nodes/trails/PresEnums.ts b/src/client/views/nodes/trails/PresEnums.ts index 564829d54..67cad9c5d 100644 --- a/src/client/views/nodes/trails/PresEnums.ts +++ b/src/client/views/nodes/trails/PresEnums.ts @@ -7,7 +7,7 @@ export enum PresMovement { } export enum PresEffect { - Zoom = 'Zoom', + Expand = 'Expand', Lightspeed = 'Lightspeed', Fade = 'Fade in', Flip = 'Flip', diff --git a/src/client/views/nodes/trails/SlideEffect.scss b/src/client/views/nodes/trails/SlideEffect.scss index cc851354e..aa2e5bbd9 100644 --- a/src/client/views/nodes/trails/SlideEffect.scss +++ b/src/client/views/nodes/trails/SlideEffect.scss @@ -8,7 +8,7 @@ .flip-side { position: absolute; will-change: transform, opacity; - backface-visibility: hidden; + // backface-visibility: hidden; } .flip-front { diff --git a/src/client/views/nodes/trails/SlideEffect.tsx b/src/client/views/nodes/trails/SlideEffect.tsx index 03cd88f45..00039e3cb 100644 --- a/src/client/views/nodes/trails/SlideEffect.tsx +++ b/src/client/views/nodes/trails/SlideEffect.tsx @@ -5,358 +5,116 @@ import { Doc } from '../../../../fields/Doc'; import { NumCast } from '../../../../fields/Types'; import { PresEffect, PresEffectDirection } from './PresEnums'; import './SlideEffect.scss'; +import { emptyFunction } from '../../../../Utils'; interface SlideEffectProps { - // pass in doc to extract width, height, bg - doc?: Doc; + doc?: Doc; // pass in doc to extract width, height, bg dir: PresEffectDirection; presEffect: PresEffect; - // stiffness (figma) = tension (react-spring) - tension: number; - // damping (figma) = friction (react-spring) - friction: number; - mass: number; + springSettings: { + stiffness: number; + damping: number; + mass: number; + }; children: React.ReactNode; infinite?: boolean; + startOpacity?: number; // set to zero to linearly fade in while animating } const DEFAULT_WIDTH = 40; const PREVIEW_OFFSET = 60; const ACTUAL_OFFSET = 200; -const infiniteOptions = { - loop: true, - delay: 500, -}; /** * This component wraps around the doc to create an effect animation, and also wraps the preview animations * for the effects as well. */ -export default function SpringAnimation({ doc, dir, friction, tension, mass, presEffect, children, infinite }: SlideEffectProps) { - const [springs, api] = useSpring( - () => ({ - from: { - x: 0, - y: 0, - opacity: 0, - scale: 1, - }, - config: { - tension, - friction, - mass, - }, - onStart: () => {}, - onRest: () => {}, - }), - [tension, friction, mass] - ); - const [ref, inView] = useInView({ - once: true, - }); - - const zoomConfig = { - from: { - scale: 0, - x: 0, - y: 0, - opacity: 1, - }, - to: { - scale: 1, - x: 0, - y: 0, - opacity: 1, - config: { - tension: tension, - friction: friction, - mass: mass, - }, - }, +export default function SpringAnimation({ doc, dir, springSettings, presEffect, children, infinite, startOpacity }: SlideEffectProps) { + const expandConfig = { + to: { scale: 1, x: 0, y: 0 }, + from: { scale: 0, x: 0, y: 0 }, }; - const fadeConfig = { - from: { - opacity: 0, - scale: 1, - x: 0, - y: 0, - }, - to: { - opacity: 1, - scale: 1, - x: 0, - y: 0, - config: { - tension: tension, - friction: friction, - mass: mass, - }, - }, + to: { x: 0, y: 0 }, + from: { x: 0, y: 0 }, }; - const rotateConfig = { - from: { - x: 0, - }, - to: { - x: 360, - config: { - tension: tension, - friction: friction, - mass: mass, - }, - }, + to: { x: 360, y: 0 }, + from: { x: 0, y: 0 }, }; - - const getBounceConfigFrom = () => { - switch (dir) { - case PresEffectDirection.Left: - return { - from: { - opacity: 0, - x: infinite ? -PREVIEW_OFFSET : -ACTUAL_OFFSET, - y: 0, - }, - }; - case PresEffectDirection.Right: - return { - from: { - opacity: 0, - x: infinite ? PREVIEW_OFFSET : ACTUAL_OFFSET, - y: 0, - }, - }; - case PresEffectDirection.Top: - return { - from: { - opacity: 0, - x: 0, - y: infinite ? -PREVIEW_OFFSET : -ACTUAL_OFFSET, - }, - }; - case PresEffectDirection.Bottom: - return { - from: { - opacity: 0, - x: 0, - y: infinite ? PREVIEW_OFFSET : ACTUAL_OFFSET, - }, - }; - default: - // no movement for center - return { - from: { - opacity: 0, - x: 0, - y: 0, - }, - }; - } - }; - - const bounceConfig = { - ...getBounceConfigFrom(), - to: [ - { - opacity: 1, - x: 0, - y: 0, - config: { - tension: tension, - friction: friction, - mass: mass, - }, - }, - ], - }; - const flipConfig = { - from: { - x: 0, - }, - to: { - x: 180, - config: { - tension: tension, - friction: friction, - mass: mass, - }, - }, + to: { x: 180, y: 0 }, + from: { x: 0, y: 0 }, }; - - // only left and right for now - const getRollConfigFrom = () => { - switch (dir) { - case PresEffectDirection.Left: - return { - from: { - opacity: 0, - x: -100, - y: -120, - }, - }; - case PresEffectDirection.Right: - return { - from: { - opacity: 0, - x: 100, - y: 120, - }, - }; - case PresEffectDirection.Top: - return { - from: { - opacity: 0, - x: -100, - y: -120, - }, - }; - case PresEffectDirection.Bottom: - return { - from: { - opacity: 0, - x: -100, - y: -120, - }, - }; - default: - // no movement for center - return { - from: { - opacity: 0, - x: 0, - y: 0, - }, - }; - } + const bounceConfig = { + to: { x: 0, y: 0 }, + from: (() => { + const offset = infinite ? PREVIEW_OFFSET : ACTUAL_OFFSET; + switch (dir) { + case PresEffectDirection.Left: return { x: -offset, y: 0, }; + case PresEffectDirection.Right: return { x: offset, y: 0, }; + case PresEffectDirection.Top: return { x: 0, y: -offset, }; + case PresEffectDirection.Bottom:return { x: 0, y: offset, }; + default: return { x: 0, y: 0, }; // no movement for center + }})(), // prettier-ignore }; - const rollConfig = { - ...getRollConfigFrom(), - to: { - opacity: 1, - x: 0, - y: 0, - config: { - tension: tension, - friction: friction, - mass: mass, - }, - }, + to: { x: 0, y: 0 }, + from: (() => { + switch (dir) { + case PresEffectDirection.Left: return { x: -100, y: -120, }; + case PresEffectDirection.Right: return { x: 100, y: 120, }; + case PresEffectDirection.Top: return { x: -100, y: -120, }; + case PresEffectDirection.Bottom: return { x: -100, y: -120, }; + default: return { x: 0, y: 0, }; // no movement for center + }})(), // prettier-ignore }; - // Switch animation depending on slide effect - const startAnimation = () => { - api.stop(); - let config: any = zoomConfig; + // prettier-ignore + const effectConfig = (() => { switch (presEffect) { - case PresEffect.Bounce: - config = bounceConfig; - break; - case PresEffect.Zoom: - config = zoomConfig; - break; - case PresEffect.Rotate: - config = rotateConfig; - break; - case PresEffect.Fade: - config = fadeConfig; - break; - case PresEffect.Flip: - config = flipConfig; - break; - case PresEffect.Roll: - config = rollConfig; - break; - case PresEffect.Lightspeed: - break; - default: - break; - } - - if (infinite) { - config = { ...config, ...infiniteOptions }; - } + case PresEffect.Fade: return fadeConfig; + case PresEffect.Bounce: return bounceConfig; + case PresEffect.Rotate: return rotateConfig; + case PresEffect.Flip: return flipConfig; + case PresEffect.Roll: return rollConfig; + case PresEffect.Lightspeed: return { from: {}, to: {} }; + case PresEffect.Expand: + default: return expandConfig; + } // prettier-ignore + })(); - api.start(config); - }; - - const getRenderDoc = () => { - switch (presEffect) { - case PresEffect.Rotate: - return ( - `rotate(${val}deg)`) }}> - {children} - - ); - case PresEffect.Flip: - return ( - // Pass in doc dimensions -
- {dir === PresEffectDirection.Bottom || dir === PresEffectDirection.Top ? ( - <> - `perspective(600px) rotateX(${val}deg)`), - width: doc ? NumCast(doc.width) : DEFAULT_WIDTH, - height: doc ? NumCast(doc.height) : DEFAULT_WIDTH, - backgroundColor: infinite ? '#a825ff' : 'rgb(223, 223, 223);', - }} - /> - `perspective(600px) rotateX(${val}deg)`), rotateX: '180deg', width: doc ? NumCast(doc.width) : DEFAULT_WIDTH, height: doc ? NumCast(doc.height) : DEFAULT_WIDTH }}> - {children} - - - ) : ( - <> - `perspective(600px) rotateY(${val}deg)`), width: doc ? NumCast(doc.width) : DEFAULT_WIDTH, height: doc ? NumCast(doc.height) : DEFAULT_WIDTH }} - /> - `perspective(600px) rotateY(${val}deg)`), rotateY: '180deg', width: doc ? NumCast(doc.width) : DEFAULT_WIDTH, height: doc ? NumCast(doc.height) : DEFAULT_WIDTH }}> - {children} - - - )} -
- ); - case PresEffect.Roll: - return ( - `translate3d(${val}%, 0, 0) rotate3d(0, 0, 1, ${val2}deg)`) }}> - {children} - - ); - default: - return ( - - {children} - - ); - } - }; - - useEffect(() => { - if (infinite || !inView) return; - setTimeout(() => { - startAnimation(); - }, 100); - }, [inView]); + const [springs, api] = useSpring( + () => ({ + to: { ...effectConfig.to, opacity: 1 }, + from: { ...effectConfig.from, opacity: startOpacity ?? 1 }, + config: { tension: springSettings.stiffness, friction: springSettings.damping, mass: springSettings.mass }, + onStart: emptyFunction, + onRest: emptyFunction, + }), + [springSettings] + ); + const [ref, inView] = useInView({ + once: true, + }); useEffect(() => { - if (infinite) { - startAnimation(); + if (inView) { + api.start({ loop: infinite, delay: infinite ? 500 : 0 }); } - }, [presEffect, tension, friction, mass]); - - return
{getRenderDoc()}
; + }, [inView]); + const animatedDiv = (style: any) => ( + `${val}`) }}> + {children} + + ); + const [width, height] = [NumCast(doc?.width, DEFAULT_WIDTH), NumCast(doc?.height, DEFAULT_WIDTH)]; + const flipAxis = dir === PresEffectDirection.Bottom || dir === PresEffectDirection.Top ? 'X' : 'Y'; + const [rotateX, rotateY] = flipAxis === 'X' ? ['180deg', undefined] : [undefined, '180deg']; + switch (presEffect) { + case PresEffect.Flip: return animatedDiv({ transform: to(springs.x, val => `perspective(600px) rotate${flipAxis}(${val}deg)`), width, height, rotateX, rotateY }) + case PresEffect.Rotate:return animatedDiv({ transform: to(springs.x, val => `rotate(${val}deg)`) }); + case PresEffect.Roll: return animatedDiv({ transform: to([springs.x, springs.y], (val, val2) => `translate3d(${val}%, 0, 0) rotate3d(0, 0, 1, ${val2}deg)`) }); + default: return animatedDiv(springs); + } // prettier-ignore } diff --git a/src/client/views/nodes/trails/SpringUtils.ts b/src/client/views/nodes/trails/SpringUtils.ts index bfb22c46a..73e1e14f1 100644 --- a/src/client/views/nodes/trails/SpringUtils.ts +++ b/src/client/views/nodes/trails/SpringUtils.ts @@ -80,7 +80,7 @@ export const effectItems = Object.values(PresEffect) export const presEffectDefaultTimings: { [key: string]: SpringSettings; } = { - Zoom: { type: SpringType.GENTLE, stiffness: 100, damping: 15, mass: 1 }, + Expand: { type: SpringType.GENTLE, stiffness: 100, damping: 15, mass: 1 }, Bounce: { type: SpringType.BOUNCY, stiffness: 600, -- cgit v1.2.3-70-g09d2 From 38a382a03675d6a50ec7de75f05025efd093f570 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 17 May 2024 10:39:25 -0400 Subject: manually added ChatBox from aj-starter --- package-lock.json | 359 ++++++++++++ package.json | 7 + src/client/documents/DocumentTypes.ts | 1 + src/client/documents/Documents.ts | 9 + src/client/util/CurrentUserUtils.ts | 2 + src/client/views/Main.tsx | 2 + src/client/views/nodes/ChatBox/ChatBox.scss | 228 ++++++++ src/client/views/nodes/ChatBox/ChatBox.tsx | 609 +++++++++++++++++++++ .../views/nodes/ChatBox/MessageComponent.tsx | 116 ++++ src/client/views/nodes/ChatBox/types.ts | 23 + src/client/views/nodes/DocumentContentsView.tsx | 2 +- src/fields/Types.ts | 5 +- src/server/ApiManagers/AssistantManager.ts | 131 +++++ src/server/index.ts | 10 +- 14 files changed, 1498 insertions(+), 6 deletions(-) create mode 100644 src/client/views/nodes/ChatBox/ChatBox.scss create mode 100644 src/client/views/nodes/ChatBox/ChatBox.tsx create mode 100644 src/client/views/nodes/ChatBox/MessageComponent.tsx create mode 100644 src/client/views/nodes/ChatBox/types.ts create mode 100644 src/server/ApiManagers/AssistantManager.ts (limited to 'src/client/documents') diff --git a/package-lock.json b/package-lock.json index 60198132c..35ffc712c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -60,6 +60,7 @@ "babel": "^6.23.0", "babel-loader": "^9.1.3", "bcrypt-nodejs": "0.0.3", + "better-react-mathjax": "^2.0.3", "bezier-curve": "^1.0.0", "bezier-js": "^6.1.4", "bingmaps-react": "^1.2.10", @@ -86,6 +87,7 @@ "csstype": "^3.1.3", "csv-parser": "^3.0.0", "csv-stringify": "^6.4.4", + "csvtojson": "^2.0.10", "D": "^1.0.0", "d3": "^7.8.5", "depcheck": "^1.4.7", @@ -107,6 +109,7 @@ "fluent-ffmpeg": "^2.1.2", "forever-agent": "^0.6.1", "fork-ts-checker-webpack-plugin": "^9.0.2", + "form-data": "^4.0.0", "formidable": "3.5.1", "function-plot": "^1.23.3", "golden-layout": "^2.6.0", @@ -133,6 +136,7 @@ "jszip": "^3.10.1", "lodash": "^4.17.21", "mapbox-gl": "^3.0.1", + "markdown-it": "^14.1.0", "mathquill": "^0.10.1-a", "md5-file": "^5.0.0", "memorystream": "^0.3.1", @@ -182,6 +186,7 @@ "react-grid-layout": "^1.4.4", "react-icons": "^5.0.1", "react-jsx-parser": "^1.29.0", + "react-latex-next": "^3.0.0", "react-loading": "^2.0.3", "react-map-gl": "^7.1.6", "react-markdown": "^9.0.1", @@ -193,8 +198,10 @@ "react-xarrows": "^2.0.2", "readline": "^1.3.0", "recharts": "^2.10.3", + "rehype-katex": "^7.0.0", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.0", + "remark-math": "^6.0.0", "request": "^2.88.2", "request-promise": "^4.2.6", "reveal.js": "^5.0.2", @@ -9347,6 +9354,11 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/katex": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.7.tgz", + "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==" + }, "node_modules/@types/keygrip": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.6.tgz", @@ -11251,6 +11263,17 @@ "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==" }, + "node_modules/better-react-mathjax": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/better-react-mathjax/-/better-react-mathjax-2.0.3.tgz", + "integrity": "sha512-wfifT8GFOKb1TWm2+E50I6DJpLZ5kLbch283Lu043EJtwSv0XvZDjr4YfR4d2MjAhqP6SH4VjjrKgbX8R00oCQ==", + "dependencies": { + "mathjax-full": "^3.2.2" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, "node_modules/bezier-curve": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/bezier-curve/-/bezier-curve-1.0.0.tgz", @@ -15907,6 +15930,33 @@ "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-6.4.6.tgz", "integrity": "sha512-h2V2XZ3uOTLilF5dPIptgUfN/o2ia/80Ie0Lly18LAnw5s8Eb7kt8rfxSUy24AztJZas9f6DPZpVlzDUtFt/ag==" }, + "node_modules/csvtojson": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/csvtojson/-/csvtojson-2.0.10.tgz", + "integrity": "sha512-lUWFxGKyhraKCW8Qghz6Z0f2l/PqB1W3AO0HKJzGIQ5JRSlR651ekJDiGJbBT4sRNNv5ddnSGVEnsxP9XRCVpQ==", + "dependencies": { + "bluebird": "^3.5.1", + "lodash": "^4.17.3", + "strip-bom": "^2.0.0" + }, + "bin": { + "csvtojson": "bin/csvtojson" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/csvtojson/node_modules/strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", + "dependencies": { + "is-utf8": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/custom-event": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", @@ -18913,6 +18963,14 @@ "node": ">=8" } }, + "node_modules/esm": { + "version": "3.2.25", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", + "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", + "engines": { + "node": ">=6" + } + }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -20825,6 +20883,52 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-from-dom": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/hast-util-from-dom/-/hast-util-from-dom-5.0.0.tgz", + "integrity": "sha512-d6235voAp/XR3Hh5uy7aGLbM3S4KamdW0WEgOaU1YoewnuYw4HXb5eRtv9g65m/RFGEfUY1Mw4UqCc5Y8L4Stg==", + "dependencies": { + "@types/hast": "^3.0.0", + "hastscript": "^8.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.1.tgz", + "integrity": "sha512-RXQBLMl9kjKVNkJTIO6bZyb2n+cUH8LFaSSzo82jiLT6Tfc+Pt7VQCS+/h3YwG4jaNE2TA2sdJisGWR+aJrp0g==", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html-isomorphic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hast-util-from-html-isomorphic/-/hast-util-from-html-isomorphic-2.0.0.tgz", + "integrity": "sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-dom": "^5.0.0", + "hast-util-from-html": "^2.0.0", + "unist-util-remove-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-from-parse5": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.1.tgz", @@ -20844,6 +20948,18 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-parse-selector": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", @@ -20924,6 +21040,21 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-whitespace": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", @@ -22592,6 +22723,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==" + }, "node_modules/is-weakmap": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", @@ -23214,6 +23350,29 @@ "node": ">=12.0.0" } }, + "node_modules/katex": { + "version": "0.16.10", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.10.tgz", + "integrity": "sha512-ZiqaC04tp2O5utMsl2TEZTXxa6WSC4yo0fv5ML++D3QZv/vx2Mct0mTlRx3O+uUkjfuAgOkzsCmq5MiUEsDDdA==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "engines": { + "node": ">= 12" + } + }, "node_modules/kdbush": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz", @@ -23429,6 +23588,14 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, "node_modules/load-bmfont": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.1.tgz", @@ -23796,6 +23963,27 @@ "vt-pbf": "^3.1.3" } }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, "node_modules/markdown-table": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz", @@ -23819,6 +24007,17 @@ "mr-parser": "^0.2.1" } }, + "node_modules/mathjax-full": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/mathjax-full/-/mathjax-full-3.2.2.tgz", + "integrity": "sha512-+LfG9Fik+OuI8SLwsiR02IVdjcnRCy5MufYLi0C3TdMT56L/pjB0alMVGgoWJF8pN9Rc7FESycZB9BMNWIid5w==", + "dependencies": { + "esm": "^3.2.25", + "mhchemparser": "^4.1.0", + "mj-context-menu": "^0.6.1", + "speech-rule-engine": "^4.0.6" + } + }, "node_modules/mathquill": { "version": "0.10.1-a", "resolved": "https://registry.npmjs.org/mathquill/-/mathquill-0.10.1-a.tgz", @@ -23987,6 +24186,24 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-math": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-math/-/mdast-util-math-3.0.0.tgz", + "integrity": "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "longest-streak": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.1.0", + "unist-util-remove-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdast-util-mdx-expression": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.0.tgz", @@ -24109,6 +24326,11 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==" + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -24172,6 +24394,11 @@ "node": ">= 0.6" } }, + "node_modules/mhchemparser": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/mhchemparser/-/mhchemparser-4.2.1.tgz", + "integrity": "sha512-kYmyrCirqJf3zZ9t/0wGgRZ4/ZJw//VwaRVGA75C4nhE60vtnIzhl9J9ndkX/h6hxSN7pjg/cE0VxbnNM+bnDQ==" + }, "node_modules/micromark": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.0.tgz", @@ -24353,6 +24580,24 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/micromark-extension-math": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.0.0.tgz", + "integrity": "sha512-iJ2Q28vBoEovLN5o3GO12CpqorQRYDPT+p4zW50tGwTfJB+iv/VnB6Ini+gqa24K97DwptMBBIvVX6Bjk49oyQ==", + "dependencies": { + "@types/katex": "^0.16.0", + "devlop": "^1.0.0", + "katex": "^0.16.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/micromark-factory-destination": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.0.tgz", @@ -24840,6 +25085,11 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/mj-context-menu": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/mj-context-menu/-/mj-context-menu-0.6.1.tgz", + "integrity": "sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA==" + }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -29576,6 +29826,14 @@ "node": ">=6" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "engines": { + "node": ">=6" + } + }, "node_modules/q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", @@ -30099,6 +30357,22 @@ "@types/react": "^17" } }, + "node_modules/react-latex-next": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/react-latex-next/-/react-latex-next-3.0.0.tgz", + "integrity": "sha512-x70f1b1G7TronVigsRgKHKYYVUNfZk/3bciFyYX1lYLQH2y3/TXku3+5Vap8MDbJhtopePSYBsYWS6jhzIdz+g==", + "dependencies": { + "katex": "^0.16.0" + }, + "engines": { + "node": ">=12", + "npm": ">=5" + }, + "peerDependencies": { + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-loading": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/react-loading/-/react-loading-2.0.3.tgz", @@ -30622,6 +30896,24 @@ "jsesc": "bin/jsesc" } }, + "node_modules/rehype-katex": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-katex/-/rehype-katex-7.0.0.tgz", + "integrity": "sha512-h8FPkGE00r2XKU+/acgqwWUlyzve1IiOKwsEkg4pDL3k48PiE0Pt+/uLtVHDVkN1yA4iurZN6UES8ivHVEQV6Q==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/katex": "^0.16.0", + "hast-util-from-html-isomorphic": "^2.0.0", + "hast-util-to-text": "^4.0.0", + "katex": "^0.16.0", + "unist-util-visit-parents": "^6.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/rehype-raw": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", @@ -30661,6 +30953,21 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/remark-math": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/remark-math/-/remark-math-6.0.0.tgz", + "integrity": "sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-math": "^3.0.0", + "micromark-extension-math": "^3.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/remark-parse": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", @@ -32129,6 +32436,27 @@ "node": ">= 6" } }, + "node_modules/speech-rule-engine": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-4.0.7.tgz", + "integrity": "sha512-sJrL3/wHzNwJRLBdf6CjJWIlxC04iYKkyXvYSVsWVOiC2DSkHmxsqOhEeMsBA9XK+CHuNcsdkbFDnoUfAsmp9g==", + "dependencies": { + "commander": "9.2.0", + "wicked-good-xpath": "1.3.0", + "xmldom-sre": "0.1.31" + }, + "bin": { + "sre": "bin/sre" + } + }, + "node_modules/speech-rule-engine/node_modules/commander": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.2.0.tgz", + "integrity": "sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w==", + "engines": { + "node": "^12.20.0 || >=14" + } + }, "node_modules/splaytree": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/splaytree/-/splaytree-3.1.2.tgz", @@ -34036,6 +34364,11 @@ "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz", "integrity": "sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg==" }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==" + }, "node_modules/uid-safe": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", @@ -34164,6 +34497,19 @@ "node": ">=0.10.0" } }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/unist-util-is": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", @@ -35165,6 +35511,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wicked-good-xpath": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz", + "integrity": "sha512-Gd9+TUn5nXdwj/hFsPVx5cuHHiF5Bwuc30jZ4+ronF1qHK5O7HD0sgmXWSEgwKquT3ClLoKPVbO6qGwVwLzvAw==" + }, "node_modules/wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", @@ -35481,6 +35832,14 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, + "node_modules/xmldom-sre": { + "version": "0.1.31", + "resolved": "https://registry.npmjs.org/xmldom-sre/-/xmldom-sre-0.1.31.tgz", + "integrity": "sha512-f9s+fUkX04BxQf+7mMWAp5zk61pciie+fFLC9hX9UVvCeJQfNHRHXpeo5MPcR0EUf57PYLdt+ZO4f3Ipk2oZUw==", + "engines": { + "node": ">=0.1" + } + }, "node_modules/xmlhttprequest-ssl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", diff --git a/package.json b/package.json index 833bebf44..aa0874714 100644 --- a/package.json +++ b/package.json @@ -145,6 +145,7 @@ "babel": "^6.23.0", "babel-loader": "^9.1.3", "bcrypt-nodejs": "0.0.3", + "better-react-mathjax": "^2.0.3", "bezier-curve": "^1.0.0", "bezier-js": "^6.1.4", "bingmaps-react": "^1.2.10", @@ -171,6 +172,7 @@ "csstype": "^3.1.3", "csv-parser": "^3.0.0", "csv-stringify": "^6.4.4", + "csvtojson": "^2.0.10", "D": "^1.0.0", "d3": "^7.8.5", "depcheck": "^1.4.7", @@ -192,6 +194,7 @@ "fluent-ffmpeg": "^2.1.2", "forever-agent": "^0.6.1", "fork-ts-checker-webpack-plugin": "^9.0.2", + "form-data": "^4.0.0", "formidable": "3.5.1", "function-plot": "^1.23.3", "golden-layout": "^2.6.0", @@ -218,6 +221,7 @@ "jszip": "^3.10.1", "lodash": "^4.17.21", "mapbox-gl": "^3.0.1", + "markdown-it": "^14.1.0", "mathquill": "^0.10.1-a", "md5-file": "^5.0.0", "memorystream": "^0.3.1", @@ -267,6 +271,7 @@ "react-grid-layout": "^1.4.4", "react-icons": "^5.0.1", "react-jsx-parser": "^1.29.0", + "react-latex-next": "^3.0.0", "react-loading": "^2.0.3", "react-map-gl": "^7.1.6", "react-markdown": "^9.0.1", @@ -278,8 +283,10 @@ "react-xarrows": "^2.0.2", "readline": "^1.3.0", "recharts": "^2.10.3", + "rehype-katex": "^7.0.0", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.0", + "remark-math": "^6.0.0", "request": "^2.88.2", "request-promise": "^4.2.6", "reveal.js": "^5.0.2", diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts index b4ad9c17d..0520dc375 100644 --- a/src/client/documents/DocumentTypes.ts +++ b/src/client/documents/DocumentTypes.ts @@ -20,6 +20,7 @@ export enum DocumentType { WEBCAM = 'webcam', // webcam CONFIG = 'config', // configuration document intended to specify a view layout configuration, but not be directly rendered (e.g., for saving the page# of a PDF, or view transform of a collection) SCRIPTING = 'script', // script editor + CHAT = 'chat', // chat with GPT about files EQUATION = 'equation', // equation editor FUNCPLOT = 'funcplot', // function plotter MAP = 'map', diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 24dd5cf37..ef1c709f0 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -180,6 +180,12 @@ export class DocumentOptions { date_range?: STRt = new StrInfo('date range for calendar', false); + chat?: STRt = new StrInfo('fields related to chatBox', false); + chat_history?: STRt = new StrInfo('chat history for chatbox', false); + chat_thread_id?: STRt = new StrInfo('thread id for chatbox', false); + chat_assistant_id?: STRt = new StrInfo('assistant id for chatbox', false); + chat_vector_store_id?: STRt = new StrInfo('assistant id for chatbox', false); + wikiData?: STRt = new StrInfo('WikiData ID related to map location'); description?: STRt = new StrInfo('description of document'); _timecodeToShow?: NUMt = new NumInfo('media timecode when document should appear (e.g., when an annotation shows up as a video plays)', false); @@ -735,6 +741,9 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.SCRIPTING), script || undefined, { ...options, layout: fieldKey ? `` /* ScriptingBox.LayoutString(fieldKey) */ : undefined }); } + export function ChatDocument(options?: DocumentOptions) { + return InstanceFromProto(Prototypes.get(DocumentType.CHAT), undefined, { ...(options || {}) }); + } // eslint-disable-next-line default-param-last export function VideoDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) { return InstanceFromProto(Prototypes.get(DocumentType.VID), new VideoField(url), options, undefined, undefined, undefined, overwriteDoc); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index ffc1304bf..09250d29d 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -368,6 +368,7 @@ pie title Minerals in my tap water {key: "Button", creator: Docs.Create.ButtonDocument, opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, title_custom: true, waitForDoubleClickToClick: 'never'}, scripts: {onClick: FollowLinkScript()?.script.originalScript ?? ""}}, {key: "Script", creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }}, {key: "DataViz", creator: opts => Docs.Create.DataVizDocument("/users/rz/Downloads/addresses.csv", opts), opts: { _width: 300, _height: 300 }}, + {key: "Chat", creator: Docs.Create.ChatDocument, opts: { _width: 300, _height: 300, }}, {key: "Header", creator: headerTemplate, opts: { _width: 300, _height: 120, _header_pointerEvents: "all", _header_height: 50, _header_fontSize: 9,_layout_autoHeightMargins: 50, _layout_autoHeight: true, treeView_HideUnrendered: true}}, {key: "ViewSlide", creator: slideView, opts: { _width: 400, _height: 300, _xMargin: 3, _yMargin: 3,}}, {key: "Trail", creator: Docs.Create.PresDocument, opts: { _width: 400, _height: 30, _type_collection: CollectionViewType.Stacking, dropAction: dropActionType.embed, treeView_HideTitle: true, _layout_fitWidth:true, layout_boxShadow: "0 0" }}, @@ -397,6 +398,7 @@ pie title Minerals in my tap water { toolTip: "Tap or drag to create a comparison box", title: "Compare", icon: "columns", dragFactory: doc.emptyComparison as Doc, clickFactory: DocCast(doc.emptyComparison)}, { toolTip: "Tap or drag to create an audio recorder", title: "Audio", icon: "microphone", dragFactory: doc.emptyAudio as Doc, clickFactory: DocCast(doc.emptyAudio), openFactoryLocation: OpenWhere.overlay}, { toolTip: "Tap or drag to create a map", title: "Map", icon: "map-marker-alt", dragFactory: doc.emptyMap as Doc, clickFactory: DocCast(doc.emptyMap)}, + { toolTip: "Tap or drag to create a chat assistant", title: "Assistant Chat", icon: "book",dragFactory: doc.emptyChat as Doc, clickFactory: DocCast(doc.emptyChat)}, { toolTip: "Tap or drag to create a screen grabber", title: "Grab", icon: "photo-video", dragFactory: doc.emptyScreengrab as Doc, clickFactory: DocCast(doc.emptyScreengrab), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}}, { toolTip: "Tap or drag to create a WebCam recorder", title: "WebCam", icon: "photo-video", dragFactory: doc.emptyWebCam as Doc, clickFactory: DocCast(doc.emptyWebCam), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}}, { toolTip: "Tap or drag to create a button", title: "Button", icon: "circle", dragFactory: doc.emptyButton as Doc, clickFactory: DocCast(doc.emptyButton)}, diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 8968acbbb..149de59b2 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -28,6 +28,7 @@ import { SchemaRowBox } from './collections/collectionSchema/SchemaRowBox'; import './global/globalScripts'; import { AudioBox } from './nodes/AudioBox'; import { ComparisonBox } from './nodes/ComparisonBox'; +import { ChatBox } from './nodes/ChatBox/ChatBox'; import { DataVizBox } from './nodes/DataVizBox/DataVizBox'; import { DocumentContentsView, HTMLtag } from './nodes/DocumentContentsView'; import { EquationBox } from './nodes/EquationBox'; @@ -136,6 +137,7 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0, message: 'cache' }; MapBox, ScreenshotBox, DataVizBox, + ChatBox, HTMLtag, ComparisonBox, LoadingBox, diff --git a/src/client/views/nodes/ChatBox/ChatBox.scss b/src/client/views/nodes/ChatBox/ChatBox.scss new file mode 100644 index 000000000..f1ad3d074 --- /dev/null +++ b/src/client/views/nodes/ChatBox/ChatBox.scss @@ -0,0 +1,228 @@ +$background-color: #f8f9fa; +$text-color: #333; +$input-background: #fff; +$button-color: #007bff; +$button-hover-color: darken($button-color, 10%); +$shadow-color: rgba(0, 0, 0, 0.075); +$border-radius: 8px; + +.chatBox { + display: flex; + flex-direction: column; + width: 100%; /* Adjust the width as needed, could be in percentage */ + height: 100%; /* Adjust the height as needed, could be in percentage */ + background-color: $background-color; + font-family: 'Helvetica Neue', Arial, sans-serif; + //margin: 20px auto; + //overflow: hidden; + + .scroll-box { + flex-grow: 1; + overflow-y: scroll; + overflow-x: hidden; + height: 100%; + padding: 10px; + display: flex; + flex-direction: column-reverse; + + &::-webkit-scrollbar { + width: 8px; + } + &::-webkit-scrollbar-thumb { + background-color: darken($background-color, 10%); + border-radius: $border-radius; + } + + + .chat-content { + display: flex; + flex-direction: column; + } + + .messages { + display: flex; + flex-direction: column; + .message { + padding: 10px; + margin-bottom: 10px; + border-radius: $border-radius; + background-color: lighten($background-color, 5%); + box-shadow: 0 2px 5px $shadow-color; + //display: flex; + align-items: center; + max-width: 70%; + word-break: break-word; + .message-footer { // Assuming this is the container for the toggle button + //max-width: 70%; + + + .toggle-logs-button { + margin-top: 10px; // Padding on sides to align with the text above + width: 95%; + //display: block; // Ensures the button extends the full width of its container + text-align: center; // Centers the text inside the button + //padding: 8px 0; // Adequate padding for touch targets + background-color: $button-color; + color: #fff; + border: none; + border-radius: $border-radius; + cursor: pointer; + //transition: background-color 0.3s; + //margin-top: 10px; // Adds space above the button + box-shadow: 0 2px 4px $shadow-color; // Consistent shadow with other elements + &:hover { + background-color: $button-hover-color; + } + } + .tool-logs { + width: 100%; + background-color: $input-background; + color: $text-color; + margin-top: 5px; + //padding: 10px; + //border-radius: $border-radius; + //box-shadow: inset 0 2px 4px $shadow-color; + //transition: opacity 1s ease-in-out; + font-family: monospace; + overflow-x: auto; + max-height: 150px; // Ensuring it does not grow too large + overflow-y: auto; + } + + } + + .custom-link { + color: lightblue; + text-decoration: underline; + cursor: pointer; + } + &.user { + align-self: flex-end; + background-color: $button-color; + color: #fff; + } + + &.chatbot { + align-self: flex-start; + background-color: $input-background; + color: $text-color; + } + + span { + flex-grow: 1; + padding-right: 10px; + } + + img { + max-width: 50px; + max-height: 50px; + border-radius: 50%; + } + } + } + padding-bottom: 0; + } + + .chat-form { + display: flex; + flex-grow: 1; + //height: 50px; + bottom: 0; + width: 100%; + padding: 10px; + background-color: $input-background; + box-shadow: inset 0 -1px 2px $shadow-color; + + input[type="text"] { + flex-grow: 1; + border: 1px solid darken($input-background, 10%); + border-radius: $border-radius; + padding: 8px 12px; + margin-right: 10px; + } + + button { + padding: 8px 16px; + background-color: $button-color; + color: #fff; + border: none; + border-radius: $border-radius; + cursor: pointer; + transition: background-color 0.3s; + + &:hover { + background-color: $button-hover-color; + } + } + margin-bottom: 0; + } +} + +.initializing-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba($background-color, 0.95); + display: flex; + justify-content: center; + align-items: center; + font-size: 1.5em; + color: $text-color; + z-index: 10; // Ensure it's above all other content (may be better solution) + + &::before { + content: 'Initializing...'; + font-weight: bold; + } +} + + +.modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + background-color: rgba(0, 0, 0, 0.4); + + .modal-content { + background-color: $input-background; + color: $text-color; + padding: 20px; + border-radius: $border-radius; + box-shadow: 0 2px 10px $shadow-color; + display: flex; + flex-direction: column; + align-items: center; + width: auto; + min-width: 300px; + + h4 { + margin-bottom: 15px; + } + + p { + margin-bottom: 20px; + } + + button { + padding: 10px 20px; + background-color: $button-color; + color: #fff; + border: none; + border-radius: $border-radius; + cursor: pointer; + margin: 5px; + transition: background-color 0.3s; + + &:hover { + background-color: $button-hover-color; + } + } + } +} diff --git a/src/client/views/nodes/ChatBox/ChatBox.tsx b/src/client/views/nodes/ChatBox/ChatBox.tsx new file mode 100644 index 000000000..880c332ac --- /dev/null +++ b/src/client/views/nodes/ChatBox/ChatBox.tsx @@ -0,0 +1,609 @@ +import { MathJaxContext } from 'better-react-mathjax'; +import { action, makeObservable, observable, observe, reaction, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import OpenAI, { ClientOptions } from 'openai'; +import { ImageFile, Message } from 'openai/resources/beta/threads/messages'; +import { RunStep } from 'openai/resources/beta/threads/runs/steps'; +import * as React from 'react'; +import { Doc } from '../../../../fields/Doc'; +import { Id } from '../../../../fields/FieldSymbols'; +import { CsvCast, DocCast, PDFCast, StrCast } from '../../../../fields/Types'; +import { CsvField } from '../../../../fields/URLField'; +import { Networking } from '../../../Network'; +import { DocUtils } from '../../../documents/DocUtils'; +import { DocumentType } from '../../../documents/DocumentTypes'; +import { Docs } from '../../../documents/Documents'; +import { DocumentManager } from '../../../util/DocumentManager'; +import { LinkManager } from '../../../util/LinkManager'; +import { ViewBoxAnnotatableComponent } from '../../DocComponent'; +import { FieldView, FieldViewProps } from '../FieldView'; +import './ChatBox.scss'; +import MessageComponent from './MessageComponent'; +import { ANNOTATION_LINK_TYPE, ASSISTANT_ROLE, AssistantMessage, DOWNLOAD_TYPE } from './types'; + +@observer +export class ChatBox extends ViewBoxAnnotatableComponent() { + @observable modalStatus = false; + @observable currentFile = { url: '' }; + @observable history: AssistantMessage[] = []; + @observable.deep current_message: AssistantMessage | undefined = undefined; + + @observable isLoading: boolean = false; + @observable isInitializing: boolean = true; + @observable expandedLogIndex: number | null = null; + @observable linked_docs_to_add: Doc[] = []; + + private openai: OpenAI; + private interim_history: string = ''; + private assistantID: string = ''; + private threadID: string = ''; + private _oldWheel: any; + private vectorStoreID: string = ''; + private mathJaxConfig: any; + private linkedCsvIDs: string[] = []; + + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(ChatBox, fieldKey); + } + constructor(props: FieldViewProps) { + super(props); + makeObservable(this); + this.openai = this.initializeOpenAI(); + this.history = []; + this.threadID = StrCast(this.dataDoc.thread_id); + this.assistantID = StrCast(this.dataDoc.assistant_id); + this.vectorStoreID = StrCast(this.dataDoc.vector_store_id); + this.openai = this.initializeOpenAI(); + if (this.assistantID === '' || this.threadID === '' || this.vectorStoreID === '') { + this.createAssistant(); + } else { + this.retrieveCsvUrls(); + this.isInitializing = false; + } + this.mathJaxConfig = { + loader: { load: ['input/asciimath'] }, + tex: { + inlineMath: [ + ['$', '$'], + ['\\(', '\\)'], + ], + displayMath: [ + ['$$', '$$'], + ['[', ']'], + ], + }, + }; + reaction( + () => this.history.map((msg: AssistantMessage) => ({ role: msg.role, text: msg.text, image: msg.image, tool_logs: msg.tool_logs, links: msg.links })), + serializableHistory => { + this.dataDoc.data = JSON.stringify(serializableHistory); + } + ); + } + + toggleToolLogs = (index: number) => { + this.expandedLogIndex = this.expandedLogIndex === index ? null : index; + }; + + retrieveCsvUrls() { + const linkedDocs = LinkManager.Instance.getAllRelatedLinks(this.Document) + .map(d => DocCast(LinkManager.getOppositeAnchor(d, this.Document))) + .map(d => DocCast(d?.annotationOn, d)) + .filter(d => d); + + linkedDocs.forEach(doc => { + const aiFieldId = StrCast(doc[this.Document[Id] + '_ai_field_id']); + if (CsvCast(doc.data)) { + this.linkedCsvIDs.push(StrCast(aiFieldId)); + console.log(this.linkedCsvIDs); + } + }); + } + + initializeOpenAI() { + const configuration: ClientOptions = { + apiKey: process.env.OPENAI_KEY, + dangerouslyAllowBrowser: true, + }; + return new OpenAI(configuration); + } + + onPassiveWheel = (e: WheelEvent) => { + if (this._props.isContentActive()) { + e.stopPropagation(); + } + }; + + createLink = (linkInfo: string, startIndex: number, endIndex: number, linkType: ANNOTATION_LINK_TYPE, annotationIndex: number = 0) => { + const text = this.interim_history; + const subString = this.current_message?.text.substring(startIndex, endIndex) ?? ''; + if (!text) return; + const textToDisplay = `${annotationIndex}`; + let fileInfo = linkInfo; + const fileName = subString.split('/')[subString.split('/').length - 1]; + if (linkType === ANNOTATION_LINK_TYPE.DOWNLOAD_FILE) { + fileInfo = linkInfo + '!!!' + fileName; + } + + const formattedLink = `[${textToDisplay}](${fileInfo}~~~${linkType})`; + console.log(formattedLink); + const newText = text.replace(subString, formattedLink); + runInAction(() => { + this.interim_history = newText; + console.log(newText); + this.current_message?.links?.push({ + start: startIndex, + end: endIndex, + url: linkType === ANNOTATION_LINK_TYPE.DOWNLOAD_FILE ? fileName : linkInfo, + id: linkType === ANNOTATION_LINK_TYPE.DOWNLOAD_FILE ? linkInfo : undefined, + link_type: linkType, + }); + }); + }; + + @action + createAssistant = async () => { + this.isInitializing = true; + try { + const vectorStore = await this.openai.beta.vectorStores.create({ + name: 'Vector Store for Assistant', + }); + const assistant = await this.openai.beta.assistants.create({ + name: 'Document Analyser Assistant', + instructions: ` + You will analyse documents with which you are provided. You will answer questions and provide insights based on the information in the documents. + For writing math formulas: + You have a MathJax render environment. + - Write all in-line equations within a single dollar sign, $, to render them as TeX (this means any time you want to use a dollar sign to represent a dollar sign itself, you must escape it with a backslash: "$"); + - Use a double dollar sign, $$, to render equations on a new line; + Example: $$x^2 + 3x$$ is output for "x² + 3x" to appear as TeX.`, + model: 'gpt-4-turbo', + tools: [{ type: 'file_search' }, { type: 'code_interpreter' }], + tool_resources: { + file_search: { + vector_store_ids: [vectorStore.id], + }, + code_interpreter: { + file_ids: this.linkedCsvIDs, + }, + }, + }); + const thread = await this.openai.beta.threads.create(); + + runInAction(() => { + this.dataDoc.assistant_id = assistant.id; + this.dataDoc.thread_id = thread.id; + this.dataDoc.vector_store_id = vectorStore.id; + this.assistantID = assistant.id; + this.threadID = thread.id; + this.vectorStoreID = vectorStore.id; + this.isInitializing = false; + }); + } catch (error) { + console.error('Initialization failed:', error); + this.isInitializing = false; + } + }; + + @action + runAssistant = async (inputText: string) => { + // Ensure an assistant and thread are created + if (!this.assistantID || !this.threadID || !this.vectorStoreID) { + await this.createAssistant(); + console.log('Assistant and thread created:', this.assistantID, this.threadID); + } + let currentText: string = ''; + let currentToolCallMessage: string = ''; + + // Send the user's input to the assistant + await this.openai.beta.threads.messages.create(this.threadID, { + role: 'user', + content: inputText, + }); + + // Listen to the streaming responses + const stream = this.openai.beta.threads.runs + .stream(this.threadID, { + assistant_id: this.assistantID, + }) + .on('runStepCreated', (runStep: RunStep) => { + currentText = ''; + runInAction(() => { + this.current_message = { role: ASSISTANT_ROLE.ASSISTANT, text: currentText, tool_logs: '', links: [] }; + }); + this.isLoading = true; + }) + .on('toolCallDelta', (toolCallDelta, snapshot) => { + this.isLoading = false; + if (toolCallDelta.type === 'code_interpreter') { + if (toolCallDelta.code_interpreter?.input) { + currentToolCallMessage += toolCallDelta.code_interpreter.input; + runInAction(() => { + if (this.current_message) { + this.current_message.tool_logs = currentToolCallMessage; + } + }); + } + if (toolCallDelta.code_interpreter?.outputs) { + currentToolCallMessage += '\n Code interpreter output:'; + toolCallDelta.code_interpreter.outputs.forEach(output => { + if (output.type === 'logs') { + runInAction(() => { + if (this.current_message) { + this.current_message.tool_logs += '\n|' + output.logs; + } + }); + } + }); + } + } + }) + .on('textDelta', (textDelta, snapshot) => { + this.isLoading = false; + currentText += textDelta.value; + runInAction(() => { + if (this.current_message) { + // this.current_message = {...this.current_message, text: current_text}; + this.current_message.text = currentText; + } + }); + }) + .on('messageDone', async event => { + console.log(event); + const textItem = event.content.find(item => item.type === 'text'); + if (textItem && textItem.type === 'text') { + const { text } = textItem; + console.log(text.value); + try { + runInAction(() => { + this.interim_history = text.value; + }); + } catch (e) { + console.error('Error parsing JSON response:', e); + } + + const { annotations } = text; + console.log('Annotations: ' + annotations); + let index = 0; + annotations.forEach(async annotation => { + console.log(' ' + annotation); + console.log(' ' + annotation.text); + if (annotation.type === 'file_path') { + const { file_path: filePath } = annotation; + const fileToDownload = filePath.file_id; + console.log(fileToDownload); + if (filePath) { + console.log(filePath); + console.log(fileToDownload); + this.createLink(fileToDownload, annotation.start_index, annotation.end_index, ANNOTATION_LINK_TYPE.DOWNLOAD_FILE); + } + } else { + const { file_citation: fileCitation } = annotation; + if (fileCitation) { + const citedFile = await this.openai.files.retrieve(fileCitation.file_id); + const citationUrl = citedFile.filename; + this.createLink(citationUrl, annotation.start_index, annotation.end_index, ANNOTATION_LINK_TYPE.DASH_DOC, index); + index++; + } + } + }); + runInAction(() => { + if (this.current_message) { + console.log('current message: ' + this.current_message.text); + this.current_message.text = this.interim_history; + this.history.push({ ...this.current_message }); + this.current_message = undefined; + } + }); + } + }) + .on('toolCallDone', toolCall => { + runInAction(() => { + if (this.current_message && currentToolCallMessage) { + this.current_message.tool_logs = currentToolCallMessage; + } + }); + }) + .on('imageFileDone', (content: ImageFile, snapshot: Message) => { + console.log('Image file done:', content); + }) + .on('end', () => { + console.log('Streaming done'); + }); + }; + + @action + goToLinkedDoc = async (link: string) => { + const linkedDocs = LinkManager.Instance.getAllRelatedLinks(this.Document) + .map(d => DocCast(LinkManager.getOppositeAnchor(d, this.Document))) + .map(d => DocCast(d?.annotationOn, d)) + .filter(d => d); + + const linkedDoc = linkedDocs.find(doc => { + const docUrl = CsvCast(doc.data, PDFCast(doc.data)).url.pathname.replace('/files/pdfs/', '').replace('/files/csvs/', ''); + console.log('URL: ' + docUrl + ' Citation URL: ' + link); + return link === docUrl; + }); + + if (linkedDoc) { + await DocumentManager.Instance.showDocument(DocCast(linkedDoc), { willZoomCentered: true }, () => {}); + } + }; + + @action + askGPT = async (event: React.FormEvent): Promise => { + event.preventDefault(); + + const textInput = event.currentTarget.elements.namedItem('messageInput') as HTMLInputElement; + const trimmedText = textInput.value.trim(); + + if (!this.assistantID || !this.threadID) { + try { + await this.createAssistant(); + } catch (err) { + console.error('Error:', err); + } + } + + if (trimmedText) { + try { + textInput.value = ''; + runInAction(() => { + this.history.push({ role: ASSISTANT_ROLE.USER, text: trimmedText }); + }); + await this.runAssistant(trimmedText); + this.dataDoc.data = this.history.toString(); + } catch (err) { + console.error('Error:', err); + } + } + }; + + @action + uploadLinks = async (linkedDocs: Doc[]) => { + if (this.isInitializing) { + console.log('Initialization in progress, upload aborted.'); + return; + } + const urls = linkedDocs.map(doc => CsvCast(doc.data, PDFCast(doc.data)).url.pathname); + const csvUrls = urls.filter(url => url.endsWith('.csv')); + console.log(this.assistantID, this.threadID, urls); + + const { openai_file_ids: openaiFileIds } = await Networking.PostToServer('/uploadPDFToVectorStore', { urls, threadID: this.threadID, assistantID: this.assistantID, vector_store_id: this.vectorStoreID }); + + linkedDocs.forEach((doc, i) => { + doc[this.Document[Id] + '_ai_field_id'] = openaiFileIds[i]; + console.log('AI Field ID: ' + openaiFileIds[i]); + }); + + if (csvUrls.length > 0) { + for (let i = 0; i < csvUrls.length; i++) { + this.linkedCsvIDs.push(openaiFileIds[urls.indexOf(csvUrls[i])]); + } + console.log('linked csvs:' + this.linkedCsvIDs); + await this.openai.beta.assistants.update(this.assistantID, { + tools: [{ type: 'file_search' }, { type: 'code_interpreter' }], + tool_resources: { + file_search: { + vector_store_ids: [this.vectorStoreID], + }, + code_interpreter: { + file_ids: this.linkedCsvIDs, + }, + }, + }); + } + }; + + downloadToComputer = (url: string, fileName: string) => { + fetch(url, { method: 'get', mode: 'no-cors', referrerPolicy: 'no-referrer' }) + .then(res => res.blob()) + .then(res => { + const aElement = document.createElement('a'); + aElement.setAttribute('download', fileName); + const href = URL.createObjectURL(res); + aElement.href = href; + aElement.setAttribute('target', '_blank'); + aElement.click(); + URL.revokeObjectURL(href); + }); + }; + + createDocumentInDash = async (url: string) => { + const fileSuffix = url.substring(url.lastIndexOf('.') + 1); + console.log(fileSuffix); + let doc: Doc | null = null; + switch (fileSuffix) { + case 'pdf': + doc = DocCast(await DocUtils.DocumentFromType('pdf', url, {})); + break; + case 'csv': + doc = DocCast(await DocUtils.DocumentFromType('csv', url, {})); + break; + case 'png': + case 'jpg': + case 'jpeg': + doc = DocCast(await DocUtils.DocumentFromType('image', url, {})); + break; + default: + console.error('Unsupported file type:', fileSuffix); + break; + } + if (doc) { + doc && this._props.addDocument?.(doc); + await DocumentManager.Instance.showDocument(doc, { willZoomCentered: true }, () => {}); + } + }; + + downloadFile = async (fileInfo: string, downloadType: DOWNLOAD_TYPE) => { + try { + console.log(fileInfo); + const [fileId, fileName] = fileInfo.split(/!!!/); + const { file_path: filePath } = await Networking.PostToServer('/downloadFileFromOpenAI', { file_id: fileId, file_name: fileName }); + const fileLink = CsvCast(new CsvField(filePath)).url.href; + if (downloadType === DOWNLOAD_TYPE.DASH) { + this.createDocumentInDash(fileLink); + } else { + this.downloadToComputer(fileLink, fileName); + } + } catch (error) { + console.error('Error downloading file:', error); + } + }; + + handleDownloadToDevice = () => { + this.downloadFile(this.currentFile.url, DOWNLOAD_TYPE.DEVICE); + this.modalStatus = false; // Close the modal after the action + this.currentFile = { url: '' }; // Reset the current file + }; + + handleAddToDash = () => { + // Assuming `downloadFile` is a method that handles adding to Dash + this.downloadFile(this.currentFile.url, DOWNLOAD_TYPE.DASH); + this.modalStatus = false; // Close the modal after the action + this.currentFile = { url: '' }; // Reset the current file + }; + + renderModal = () => { + if (!this.modalStatus) return null; + + return ( +
+
+

File Actions

+

Choose an action for the file:

+ + + +
+
+ ); + }; + @action + showModal = () => { + this.modalStatus = true; + }; + + @action + setCurrentFile = (file: { url: string }) => { + this.currentFile = file; + }; + + componentDidMount() { + this._props.setContentViewBox?.(this); + if (this.dataDoc.data) { + try { + const storedHistory = JSON.parse(StrCast(this.dataDoc.data)); + runInAction(() => { + this.history = storedHistory.map((msg: AssistantMessage) => ({ + role: msg.role, + text: msg.text, + quote: msg.quote, + tool_logs: msg.tool_logs, + image: msg.image, + })); + }); + } catch (e) { + console.error('Failed to parse history from dataDoc:', e); + } + } + reaction( + () => { + const linkedDocs = LinkManager.Instance.getAllRelatedLinks(this.Document) + .map(d => DocCast(LinkManager.getOppositeAnchor(d, this.Document))) + .map(d => DocCast(d?.annotationOn, d)) + .filter(d => d); + return linkedDocs; + }, + + linked => this.linked_docs_to_add.push(...linked.filter(linkedDoc => !this.linked_docs_to_add.includes(linkedDoc))) + ); + + observe( + // right now this skips during initialization which is necessary because it would be blank + // However, it will upload the same link twice when it is + this.linked_docs_to_add, + change => { + // observe pushes/splices on a user link DB 'data' field (should only happen for local changes) + switch (change.type as any) { + case 'splice': + if ((change as any).addedCount > 0) { + // maybe check here if its already in the urls datadoc array so doesn't add twice + console.log((change as any).added as Doc[]); + this.uploadLinks((change as any).added as Doc[]); + } + // (change as any).removed.forEach((link: any) => remLinkFromDoc(toRealField(link))); + break; + case 'update': // let oldValue = change.oldValue; + default: + } + }, + true + ); + } + + render() { + return ( + +
+ {this.isInitializing &&
Initializing...
} + {this.renderModal()} +
{ + this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel); + this._oldWheel = r; + r?.addEventListener('wheel', this.onPassiveWheel, { passive: false }); + }}> +
+ {this.history.map((message, index) => ( + + ))} + {!this.current_message ? null : ( + + )} +
+
+
+ + +
+
+
+ ); + } +} + +Docs.Prototypes.TemplateMap.set(DocumentType.CHAT, { + layout: { view: ChatBox, dataField: 'data' }, + options: { acl: '', chat: '', chat_history: '', chat_thread_id: '', chat_assistant_id: '', chat_vector_store_id: '' }, +}); diff --git a/src/client/views/nodes/ChatBox/MessageComponent.tsx b/src/client/views/nodes/ChatBox/MessageComponent.tsx new file mode 100644 index 000000000..fced0b4d5 --- /dev/null +++ b/src/client/views/nodes/ChatBox/MessageComponent.tsx @@ -0,0 +1,116 @@ +/* eslint-disable react/require-default-props */ +import React from 'react'; +import { observer } from 'mobx-react'; +import { MathJax, MathJaxContext } from 'better-react-mathjax'; +import ReactMarkdown from 'react-markdown'; +import { TbCircle0Filled, TbCircle1Filled, TbCircle2Filled, TbCircle3Filled, TbCircle4Filled, TbCircle5Filled, TbCircle6Filled, TbCircle7Filled, TbCircle8Filled, TbCircle9Filled } from 'react-icons/tb'; +import { AssistantMessage } from './types'; + +interface MessageComponentProps { + message: AssistantMessage; + toggleToolLogs: (index: number) => void; + expandedLogIndex: number | null; + index: number; + showModal: () => void; + goToLinkedDoc: (url: string) => void; + setCurrentFile: (file: { url: string }) => void; + isCurrent?: boolean; +} + +const MessageComponent: React.FC = function ({ message, toggleToolLogs, expandedLogIndex, goToLinkedDoc, index, showModal, setCurrentFile, isCurrent = false }) { + // const messageClass = `${message.role} ${isCurrent ? 'current-message' : ''}`; + + const LinkRenderer = ({ href, children }: { href: string; children: React.ReactNode }) => { + // console.log(href + " " + children) + const regex = /([a-zA-Z0-9_.!-]+)~~~(citation|file_path)/; + const matches = href.match(regex); + // console.log(href) + // console.log(matches) + const url = matches ? matches[1] : href; + const linkType = matches ? matches[2] : null; + if (linkType === 'citation') { + switch (children) { + case '0': + children = ; + break; + case '1': + children = ; + break; + case '2': + children = ; + break; + case '3': + children = ; + break; + case '4': + children = ; + break; + case '5': + children = ; + break; + case '6': + children = ; + break; + case '7': + children = ; + break; + case '8': + children = ; + break; + case '9': + children = ; + break; + default: + break; + } + } + // console.log(linkType) + const style = { + color: 'lightblue', + verticalAlign: linkType === 'citation' ? 'super' : 'baseline', + fontSize: linkType === 'citation' ? 'smaller' : 'inherit', + }; + + return ( + { + e.preventDefault(); + if (linkType === 'citation') { + goToLinkedDoc(url); + } else if (linkType === 'file_path') { + showModal(); + setCurrentFile({ url }); + } + }} + style={style}> + {children} + + ); + }; + + return ( +
+ + + {message.text ? message.text : ''} + + + {message.image && } +
+ {message.tool_logs && ( + + )} + {expandedLogIndex === index && ( +
+
{message.tool_logs}
+
+ )} +
+
+ ); +}; + +export default observer(MessageComponent); diff --git a/src/client/views/nodes/ChatBox/types.ts b/src/client/views/nodes/ChatBox/types.ts new file mode 100644 index 000000000..8212a7050 --- /dev/null +++ b/src/client/views/nodes/ChatBox/types.ts @@ -0,0 +1,23 @@ +export enum ASSISTANT_ROLE { + USER = 'User', + ASSISTANT = 'Assistant', +} + +export enum ANNOTATION_LINK_TYPE { + DASH_DOC = 'citation', + DOWNLOAD_FILE = 'file_path', +} + +export enum DOWNLOAD_TYPE { + DASH = 'dash', + DEVICE = 'device', +} + +export interface AssistantMessage { + role: ASSISTANT_ROLE; + text: string; + quote?: string; + image?: string; + tool_logs?: string; + links?: { start: number; end: number; url: string; id?: string; link_type: ANNOTATION_LINK_TYPE }[]; +} diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 18529a429..192c7875e 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -79,7 +79,7 @@ export class DocumentContentsView extends ObservableReactComponent = T extends string ? 'string' : T extends number ? 'number' : T extends boolean ? 'boolean' : T extends List ? ListSpec : new (...args: any[]) => T; @@ -122,6 +122,9 @@ export function CsvCast(field: FieldResult, defaultVal: CsvField | null = null) export function WebCast(field: FieldResult, defaultVal: WebField | null = null) { return Cast(field, WebField, defaultVal); } +export function PDFCast(field: FieldResult, defaultVal: PdfField | null = null) { + return Cast(field, PdfField, defaultVal); +} export function ImageCast(field: FieldResult, defaultVal: ImageField | null = null) { return Cast(field, ImageField, defaultVal); } diff --git a/src/server/ApiManagers/AssistantManager.ts b/src/server/ApiManagers/AssistantManager.ts new file mode 100644 index 000000000..82e48167a --- /dev/null +++ b/src/server/ApiManagers/AssistantManager.ts @@ -0,0 +1,131 @@ +import * as fs from 'fs'; +import { createReadStream, writeFile } from 'fs'; +import OpenAI from 'openai'; +import * as path from 'path'; +import { promisify } from 'util'; +import * as uuid from 'uuid'; +import { filesDirectory, publicDirectory } from '..'; +import { Method } from '../RouteManager'; +import ApiManager, { Registration } from './ApiManager'; + +export enum Directory { + parsed_files = 'parsed_files', + images = 'images', + videos = 'videos', + pdfs = 'pdfs', + text = 'text', + pdf_thumbnails = 'pdf_thumbnails', + audio = 'audio', + csv = 'csv', +} + +export function serverPathToFile(directory: Directory, filename: string) { + return path.normalize(`${filesDirectory}/${directory}/${filename}`); +} + +export function pathToDirectory(directory: Directory) { + return path.normalize(`${filesDirectory}/${directory}`); +} + +export function clientPathToFile(directory: Directory, filename: string) { + return `/files/${directory}/${filename}`; +} + +const writeFileAsync = promisify(writeFile); +const readFileAsync = promisify(fs.readFile); + +export default class AssistantManager extends ApiManager { + protected initialize(register: Registration): void { + const openai = new OpenAI({ apiKey: process.env.OPENAI_KEY, dangerouslyAllowBrowser: true }); + + register({ + method: Method.POST, + subscription: '/uploadPDFToVectorStore', + secureHandler: async ({ req, res }) => { + const { urls, threadID, assistantID, vector_store_id } = req.body; + + const csvFilesIds: string[] = []; + const otherFileIds: string[] = []; + const allFileIds: string[] = []; + + const fileProcesses = urls.map(async (source: string) => { + const fullPath = path.join(publicDirectory, source); + const fileData = await openai.files.create({ file: createReadStream(fullPath), purpose: 'assistants' }); + allFileIds.push(fileData.id); + if (source.endsWith('.csv')) { + console.log(source); + csvFilesIds.push(fileData.id); + } else { + openai.beta.vectorStores.files.create(vector_store_id, { file_id: fileData.id }); + otherFileIds.push(fileData.id); + } + }); + try { + await Promise.all(fileProcesses).then(() => { + res.send({ vector_store_id: vector_store_id, openai_file_ids: allFileIds }); + }); + } catch (error) { + res.status(500).send({ error: 'Failed to process files' + error }); + } + }, + }); + + register({ + method: Method.POST, + subscription: '/downloadFileFromOpenAI', + secureHandler: async ({ req, res }) => { + const { file_id, file_name } = req.body; + //let files_directory: string; + let files_directory = '/files/openAIFiles/'; + switch (file_name.split('.').pop()) { + case 'pdf': + files_directory = '/files/pdfs/'; + break; + case 'csv': + files_directory = '/files/csv/'; + break; + case 'png': + case 'jpg': + case 'jpeg': + files_directory = '/files/images/'; + break; + default: + break; + } + + const directory = path.join(publicDirectory, files_directory); + + if (!fs.existsSync(directory)) { + fs.mkdirSync(directory); + } + const file = await openai.files.content(file_id); + const new_file_name = `${uuid.v4()}-${file_name}`; + const file_path = path.join(directory, new_file_name); + const file_array_buffer = await file.arrayBuffer(); + const bufferView = new Uint8Array(file_array_buffer); + try { + const written_file = await writeFileAsync(file_path, bufferView); + console.log(written_file); + console.log(file_path); + console.log(file_array_buffer); + console.log(bufferView); + const file_object = new File([bufferView], file_name); + //DashUploadUtils.upload(file_object, 'openAIFiles'); + res.send({ file_path: path.join(files_directory, new_file_name) }); + /* res.send( { + source: "file", + result: { + accessPaths: { + agnostic: {client: path.join('/files/openAIFiles/', `${uuid.v4()}-${file_name}`)} + }, + rawText: "", + duration: 0, + }, + } ); */ + } catch (error) { + res.status(500).send({ error: 'Failed to write file' + error }); + } + }, + }); + } +} diff --git a/src/server/index.ts b/src/server/index.ts index 1bbf8a105..3151c2975 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -3,7 +3,7 @@ import * as dotenv from 'dotenv'; import * as mobileDetect from 'mobile-detect'; import * as path from 'path'; import { logExecution } from './ActionUtilities'; -import { AdminPrivileges, resolvedPorts } from './SocketData'; +import AssistantManager from './ApiManagers/AssistantManager'; import DataVizManager from './ApiManagers/DataVizManager'; import DeleteManager from './ApiManagers/DeleteManager'; import DownloadManager from './ApiManagers/DownloadManager'; @@ -13,16 +13,17 @@ import SessionManager from './ApiManagers/SessionManager'; import UploadManager from './ApiManagers/UploadManager'; import UserManager from './ApiManagers/UserManager'; import UtilManager from './ApiManagers/UtilManager'; -import { GoogleCredentialsLoader, SSL } from './apis/google/CredentialsLoader'; -import { GoogleApiServerUtils } from './apis/google/GoogleApiServerUtils'; import { DashSessionAgent } from './DashSession/DashSessionAgent'; import { AppliedSessionAgent } from './DashSession/Session/agents/applied_session_agent'; import { DashStats } from './DashStats'; import { DashUploadUtils } from './DashUploadUtils'; -import { Database } from './database'; import { Logger } from './ProcessFactory'; import RouteManager, { Method, PublicHandler } from './RouteManager'; import RouteSubscriber from './RouteSubscriber'; +import { AdminPrivileges, resolvedPorts } from './SocketData'; +import { GoogleCredentialsLoader, SSL } from './apis/google/CredentialsLoader'; +import { GoogleApiServerUtils } from './apis/google/GoogleApiServerUtils'; +import { Database } from './database'; import initializeServer from './server_Initialization'; // import GooglePhotosManager from './ApiManagers/GooglePhotosManager'; @@ -72,6 +73,7 @@ function routeSetter({ addSupervisedRoute, logRegistrationOutcome }: RouteManage new UtilManager(), new GeneralGoogleManager(), /* new GooglePhotosManager(), */ new DataVizManager(), + new AssistantManager(), ]; // initialize API Managers -- cgit v1.2.3-70-g09d2 From 6c7615110dcf8334196bbfddfd89b684e4ceee4f Mon Sep 17 00:00:00 2001 From: aidahosa1 Date: Fri, 17 May 2024 21:30:02 -0400 Subject: GOD IS GOOD --- src/client/apis/gpt/GPT.ts | 2 +- src/client/documents/Documents.ts | 2 + src/client/util/CurrentUserUtils.ts | 29 ++- .../views/collections/CollectionCardDeckView.tsx | 231 +++++++++++++-------- src/client/views/global/globalScripts.ts | 153 ++++++++++---- src/client/views/pdf/GPTPopup/GPTPopup.scss | 6 +- src/client/views/pdf/GPTPopup/GPTPopup.tsx | 102 +++++---- 7 files changed, 343 insertions(+), 182 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts index 9a24c808c..398f8ae39 100644 --- a/src/client/apis/gpt/GPT.ts +++ b/src/client/apis/gpt/GPT.ts @@ -20,7 +20,7 @@ const callTypeMap: { [type: string]: GPTCallOpts } = { summary: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Summarize this text in simpler terms: ' }, edit: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Reword this: ' }, completion: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: '' }, - sort:{model:'gpt-4o',maxTokens:2048,temp:0.5,prompt:"I'm going to give you a list of descriptions. Each one is seperated by ====== on either side. They will vary in length, so make sure to only seperate when you see ======. Sort them into lists by shared content. MAKE SURE EACH DESCRIPTOR IS IN ONLY ONE LIST. Generate only the list with each list seperated by ====== with the elements seperated by ~~~~~~"}, + sort:{model:'gpt-4o',maxTokens:2048,temp:0.5,prompt:"I'm going to give you a list of descriptions. Each one is seperated by ====== on either side. They will vary in length, so make sure to only seperate when you see ======. Sort them into lists by shared content. MAKE SURE EACH DESCRIPTOR IS IN ONLY ONE LIST. Generate only the list with each list seperated by ====== with the elements seperated by ~~~~~~. Try to do around 4 groups, but a little more or less is ok."}, describe:{model:'gpt-4-vision-preview',maxTokens:2048,temp:0,prompt:"Describe these images in 3-5 words"}, diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 57f91399a..eded2b485 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -502,6 +502,8 @@ export class DocumentOptions { cardSort?: STRt = new StrInfo('way cards are sorted in deck view'); customSortNumber?: NUMt = new NumInfo('number of custom sorts the user has created'); customHashMap?: List + visibleGroupNumbers?: List + // card_sort_time?: BOOLt = new BoolInfo('whether sorting cards in deck view by time'); // card_sort_type?: BOOLt = new BoolInfo('whether sorting cards in deck view by type'); // card_sort_color?: BOOLt = new BoolInfo('whether sorting cards in deck view by color'); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index e18c22bac..251771e8e 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -729,18 +729,34 @@ pie title Minerals in my tap water { title: "Time", icon:"hourglass-half", toolTip:"Sort by most recent document creation", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"time", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, { title: "Type", icon:"eye", toolTip:"Sort by document type", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"docType", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, { title: "Color", icon:"palette", toolTip:"Sort by document color", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"color", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, - { title: "Links", icon:"link", toolTip:"Sort by its links", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"links", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, - { title: "Create", icon:"heart", toolTip:"Create your first custom grouping!", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"custom1", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, - { title: "Create", icon:"star", toolTip:"Create your second custom grouping!", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"custom2", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, - { title: "Create", icon:"satellite", toolTip:"Create your third custom grouping!", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"custom3", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, - { title: "Create", icon:"robot", toolTip:"Have ChatGPT sort your text-based nodes !", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"chat", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + { title: "Smart Sort", icon:"robot", toolTip:"Have ChatGPT sort your text-based nodes !", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"chat", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + ] + } + static customCardTools(): Button[] { + return [ + { title: "Create", icon:"heart", toolTip:"Create your first custom grouping!", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"custom1", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + { title: "Create", icon:"star", toolTip:"Create your second custom grouping!", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"custom2", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + { title: "Create", icon:"satellite", toolTip:"Create your third custom grouping!", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"custom3", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + { title: "Custom 1", icon: "Custom 1", width: 10, toolTip: "Set visibilty!", subMenu: CurrentUserUtils.cardGroupTools("heart"), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden: `!isThatCardGroup(0)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available + { title: "Custom 2", icon: "Custom 2", width: 80, toolTip: "Set visibilty!", subMenu: CurrentUserUtils.cardGroupTools("star"), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden: `!isThatCardGroup(1)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available + { title: "Custom 3", icon: "Custom 3", width: 80, toolTip: "Set visibilty!", subMenu: CurrentUserUtils.cardGroupTools("satellite"), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden: `!isThatCardGroup(2)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available + ] + } - // ...customs + static cardGroupTools(icon: string): Button[] { + return [ + { title: "", icon:icon, toolTip:"Click to toggle visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"1", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + { title: "", icon:icon, toolTip:"Click to toggle visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"2", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + { title: "", icon:icon, toolTip:"Click to toggle visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"3", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + { title: "", icon:icon, toolTip:"Click to toggle visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"4", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, ] } + + + static viewTools(): Button[] { return [ { title: "Snap", icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"snaplines", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform @@ -842,6 +858,7 @@ pie title Minerals in my tap water { title: "View", icon: "View", toolTip: "View tools", subMenu: CurrentUserUtils.viewTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available { title: "Stack", icon: "View", toolTip: "Stacking tools", subMenu: CurrentUserUtils.stackTools(), expertMode: false, toolType:CollectionViewType.Stacking, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available { title: "Card", icon: "Sort", toolTip: "Card sort", subMenu: CurrentUserUtils.cardTools(), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available + { title: "CustomCard", icon: "Create", toolTip: "Create custom groupings!", subMenu: CurrentUserUtils.customCardTools(), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available { title: "Web", icon: "Web", toolTip: "Web functions", subMenu: CurrentUserUtils.webTools(), expertMode: false, toolType:DocumentType.WEB, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when Web is selected { title: "Video", icon: "Video", toolTip: "Video functions", subMenu: CurrentUserUtils.videoTools(), expertMode: false, toolType:DocumentType.VID, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when video is selected { title: "Image", icon: "Image", toolTip: "Image functions", subMenu: CurrentUserUtils.imageTools(), expertMode: false, toolType:DocumentType.IMG, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when image is selected diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index e96fbc161..42c82ca74 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -24,6 +24,7 @@ import { ImageField, PdfField, URLField } from '../../../fields/URLField'; import { GPTPopup } from '../pdf/GPTPopup/GPTPopup'; import { GPTPopupMode } from '../pdf/GPTPopup/GPTPopup'; import { reaction } from 'mobx'; +import { NumListCast } from '../../../fields/Doc'; @observer export class CollectionCardView extends CollectionSubView() { @@ -32,8 +33,42 @@ export class CollectionCardView extends CollectionSubView() { // documents themselves @observable hoveredNodeIndex = -1; + //index to group + @observable customGroupDictionary: Map[] = [new Map(), new Map(), new Map()]; + @computed get myChildLayoutPairs() { + let activeGroups = NumListCast(this._props.Document.visibleGroupNumbers); + let currCustom = NumCast(this._props.Document.customSortNumber); + + // console.log("Active Groups:", activeGroups); + + for (let i=0; i< activeGroups.length; i++){ + console.log("Active Groups" + activeGroups[i]) + } + // console.log("Current Custom Sort Number:", currCustom); + + if (activeGroups.length <= 0) { + return this.childLayoutPairs.filter(l => l.layout.type != DocumentType.LINK); + } + + if (StrCast(this._props.Document.cardSort).includes("custom")) { + return this.childLayoutPairs.filter((l, index) => { + if (l.layout.type === DocumentType.LINK) { + return false; + } + + // Get the group number for the current index from the customGroupDictionary + const groupNumber = this.customGroupDictionary[currCustom].get(index); + + // console.log(`Index: ${index}, Group Number: ${groupNumber}`); + + // Check if the group number is in the active groups + return groupNumber !== undefined && activeGroups.includes(groupNumber); + }); + } + + // Default return for non-custom cardSort or other cases, filtering out links return this.childLayoutPairs.filter(l => l.layout.type != DocumentType.LINK); } @@ -90,6 +125,9 @@ export class CollectionCardView extends CollectionSubView() { if (this._props.Document.customHashMap != undefined){ this.customGroupDictionary = this.getCustoms(StrListCast(this._props.Document.customHashMap)) } + // this._props.Document.visibleGroupNumbers = new List([1,2,3,4]) + + reaction( () => this._props.Document.cardSort, @@ -101,7 +139,6 @@ export class CollectionCardView extends CollectionSubView() { ); } - @observable customGroupDictionary: Map[] = [new Map(), new Map(), new Map()]; @computed get mapToField(): List { const resultList = new List(); @@ -272,7 +309,8 @@ export class CollectionCardView extends CollectionSubView() { case 'custom': return this.sort(sorted, 'custom', desc); case 'chat': - return { docs: this.sortedDocs || this.myChildLayoutPairs}; // Use the sorted docs from the observable + return this.sort(this.myChildLayoutPairs, 'gpt', BoolCast(this.layoutDoc.sortDesc)); + default: docs = this.myChildLayoutPairs; return { docs }; @@ -458,7 +496,8 @@ export class CollectionCardView extends CollectionSubView() { // const childPair = { layout: doc, data: doc }; const isHovered = this.hoveredNodeIndex === index; - const childPairIndex = this.myChildLayoutPairs.indexOf(childPair); + // const childPairIndex = this.myChildLayoutPairs.indexOf(childPair); + const childPairIndex = this.childLayoutPairs.filter(d => d.layout.type != DocumentType.LINK).indexOf(childPair) const realIndex = this.sortedDocsType.docs.filter(d => !SelectionManager.IsSelected(d.layout)).indexOf(childPair); const calcRowIndex = this.overflowIndexCalc(realIndex); @@ -501,6 +540,8 @@ export class CollectionCardView extends CollectionSubView() { {this.displayDoc(childPair, childScreenToLocal)} {this._props.Document.cardSort == 'custom' ? this.renderButtons(childPairIndex) : ''} + {this._props.Document.cardSort == 'chat' ? this.renderButtons(childPairIndex, childPair.layout, true) : ''} +
); }); @@ -512,38 +553,23 @@ export class CollectionCardView extends CollectionSubView() { - @action toggleButton(childPairIndex: number, buttonID: number) { - this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].set(childPairIndex, buttonID); - this._props.Document.customHashMap = this.mapToField - - // Cast(selected.Document.data, WebField, null)?.url?.href) - - //note richtext - - // StrCast(selected.Document.data, Cast(selected.Document.data, WebField, null)?.url?.href) - - - - // const - const imgurlperchance = Cast(this.childDocs[1].data, ImageField, null)?.url.href; - const perchance = this.imageUrlToBase64(imgurlperchance) - // const imgurlperchance = Cast(this.childDocs[1].data, PdfField, null)?.url.; - - - // console.log('Print Front of cards: ' + RTFCast(DocCast(this.dataDoc[this.fieldKey + '_0']).text)?.Text); - // console.log('Print Back of cards: ' + RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text); - - // const perchance = StrCast(this.childDocs[0].text); - - - - // const pdf = (StrCast(this.myChildLayoutPairs[0].layout.text)); //pdf - const queryText = this.myChildLayoutPairs[0].layout.type; //everything else - - // const queryText = RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text; - - console.log(queryText ?? "sad") + @action toggleButton(childPairIndex: number, buttonID: number, isChat = false, doc?: Doc) { + if (!isChat) { + this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].set(childPairIndex, buttonID); + this._props.Document.customHashMap = this.mapToField; + } + + if (isChat && doc) { + this.gptGroups.set(doc, buttonID); + } + + // console.log(`Toggled button for childPairIndex: ${childPairIndex}, buttonID: ${buttonID}, isChat: ${isChat}`); + // console.log(`Updated customGroupDictionary:`, this.customGroupDictionary); + if (isChat && doc) { + console.log(`Updated gptGroups for doc ${doc}:`, this.gptGroups.get(doc)); + } } + async childPairStringList(): Promise { let string = ""; @@ -598,20 +624,25 @@ export class CollectionCardView extends CollectionSubView() { textToDoc = new Map() gptProccessedImages = new Set() - @action async getImageDesc(image: Doc){ - let href = (image['data'] as URLField).url.href; - let hrefParts = href.split('.'); - let hrefComplete = `${hrefParts[0]}_o.${hrefParts[1]}`; - try { - let hrefBase64 = await this.imageUrlToBase64(hrefComplete); - let response = await gptImageLabel(hrefBase64); - this.textToDoc.set(response.trim(), image); - this.gptProccessedImages.add(image); - console.log(response); - return response; // Return the response from gptImageLabel - } catch (error) { - console.log("bad things have happened"); - } + @action async getImageDesc(image: Doc) { + if (this.gptProccessedImages.has(image)) { + // Return the already processed description + return Array.from(this.textToDoc.keys()).find(key => this.textToDoc.get(key) === image) || ''; + } + + let href = (image['data'] as URLField).url.href; + let hrefParts = href.split('.'); + let hrefComplete = `${hrefParts[0]}_o.${hrefParts[1]}`; + try { + let hrefBase64 = await this.imageUrlToBase64(hrefComplete); + let response = await gptImageLabel(hrefBase64); + this.textToDoc.set(response.trim(), image); + this.gptProccessedImages.add(image); + console.log(response); + return response; // Return the response from gptImageLabel + } catch (error) { + console.log("bad things have happened"); + } } @@ -620,41 +651,43 @@ export class CollectionCardView extends CollectionSubView() { //a map from the text to the Doc (for everything) - @action async smartSort() { - this.isLoading = true; - console.log("loading"); + // @action async smartSort() { + // this.isLoading = true; + // console.log("loading"); - // Store the result of childPairStringList in a variable - const childPairStrings = await this.childPairStringList(); + // // Store the result of childPairStringList in a variable + // const childPairStrings = await this.childPairStringList(); - if (childPairStrings === "") { - console.log("no child pairs :("); - } else { - console.log(childPairStrings + " og list"); - let prompt = childPairStrings; + // if (childPairStrings === "") { + // console.log("no child pairs :("); + // } else { + // console.log(childPairStrings + " og list"); + // let prompt = childPairStrings; - let res = await gptAPICall(prompt, GPTCallType.SORT); - this.isLoading = false; - if (res == 'Error connecting with API.') { - // If GPT call failed - console.error('GPT call failed'); - } else if (res != null) { - console.log(res); - this.processGptOutput(res); - // Update the observable with the sorted documents - this.sortedDocs = this.sort(this.myChildLayoutPairs, 'gpt', BoolCast(this.layoutDoc.sortDesc)).docs; - } - this.isLoading = false; - } - } + // let res = await gptAPICall(prompt, GPTCallType.SORT); + // this.isLoading = false; + // if (res == 'Error connecting with API.') { + // // If GPT call failed + // console.error('GPT call failed'); + // } else if (res != null) { + // console.log(res); + // this.processGptOutput(res); + // // Update the observable with the sorted documents + // this.sortedDocs = this.sort(this.myChildLayoutPairs, 'gpt', BoolCast(this.layoutDoc.sortDesc)).docs; + // } + // this.isLoading = false; + // } + // } - gptGroups = new Map + gptGroups = new ObservableMap + @observable amGPTGroups = 0 // Method to convert the GPT-produced string into a map processGptOutput(gptOutput: string) { // Split the string into individual list items const listItems = gptOutput.split('======').filter(item => item.trim() !== ''); + this.amGPTGroups = listItems.length listItems.forEach((item, index) => { // Split the item by '~~~~~~' to get all descriptors @@ -678,14 +711,15 @@ export class CollectionCardView extends CollectionSubView() { @observable isChatPopupOpen = false; @action openChatPopup = async () => { - this.isChatPopupOpen = true; - GPTPopup.Instance.setVisible(true); - GPTPopup.Instance.setMode(GPTPopupMode.SORT); - - // Await the promise to get the string result - const sortDesc = await this.childPairStringList(); - GPTPopup.Instance.setSortDesc(sortDesc); - GPTPopup.Instance.onSortComplete = this.handleGptSortResult; + this.isChatPopupOpen = true; + GPTPopup.Instance.setVisible(true); + GPTPopup.Instance.setMode(GPTPopupMode.SORT); + + // Await the promise to get the string result + const sortDesc = await this.childPairStringList(); + GPTPopup.Instance.setCardsDoneLoading(true); // Set dataDoneLoading to true after data is loaded + GPTPopup.Instance.setSortDesc(sortDesc); + GPTPopup.Instance.onSortComplete = this.handleGptSortResult; }; @action handleGptSortResult = (sortResult: string) => { this.processGptOutput(sortResult); @@ -696,19 +730,34 @@ export class CollectionCardView extends CollectionSubView() { - renderButtons(childPairIndex: number) { + renderButtons(childPairIndex: number, doc?: Doc, isChat = false) { const buttons = []; // Array to hold the button elements - - let amButtons = 4; - - let activeButtonIndex = this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].get(childPairIndex); - + const groupNumber = NumCast(this._props.Document.customSortNumber); + + let amButtons = 4; // Adjusted to your context + let activeButtonIndex = this.customGroupDictionary[groupNumber].get(childPairIndex); + + if (isChat && doc) { + if (this.amGPTGroups > 4){ + amButtons = this.amGPTGroups; + } + activeButtonIndex = this.gptGroups.get(doc); + } + + console.log("childPairIndex:", childPairIndex, "activeButtonIndex:", activeButtonIndex, "groupNumber:", groupNumber); + for (let i = 0; i < amButtons; i++) { - const isActive = activeButtonIndex == i; - - buttons.push(); + const isActive = activeButtonIndex === i; + console.log(`Rendering button ${i} for childPairIndex ${childPairIndex} isActive: ${isActive}`); + buttons.push( + + ); } - + const totalWidth = amButtons * 35 + amButtons * 2 * 5 + 6; return (
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 14f83beb6..496d7482c 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -22,7 +22,8 @@ import { VideoBox } from '../nodes/VideoBox'; import { WebBox } from '../nodes/WebBox'; import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; import { ImageBox } from '../nodes/ImageBox'; - +import { NumListCast } from '../../../fields/Doc'; +import { List } from '../../../fields/List'; ScriptingGlobals.add(function IsNoneSelected() { return SelectionManager.Views.length <= 0; }, 'are no document selected'); @@ -107,84 +108,146 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) { -ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'custom1' | 'custom2' | 'custom3'| 'chat', checkResult?: boolean, persist?: boolean, customNumber?: number) { +ScriptingGlobals.add(function showFreeform( + attr: 'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'custom1' | 'custom2' | 'custom3' | 'chat' | '1' | '2' | '3' | '4', + checkResult?: boolean, + persist?: boolean, + isDoubleClick?: boolean +) { const selected = SelectionManager.Docs.lastElement(); // prettier-ignore - const map: Map<'flashcards' | 'center' |'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'custom1' | 'custom2' | 'custom3'| 'chat', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([ + const map: Map<'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'custom1' | 'custom2' | 'custom3' | 'chat' | '1' | '2' | '3' | '4', + { + waitForRender?: boolean; + checkResult: (doc: Doc) => any; + setDoc: (doc: Doc, dv: DocumentView) => void; + }> = new Map([ ['grid', { - checkResult: (doc:Doc) => BoolCast(doc?._freeform_backgroundGrid, false), - setDoc: (doc:Doc,dv:DocumentView) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid, + checkResult: (doc: Doc) => BoolCast(doc?._freeform_backgroundGrid, false), + setDoc: (doc: Doc, dv: DocumentView) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid, }], ['snaplines', { - checkResult: (doc:Doc) => BoolCast(doc?._freeform_snapLines, false), - setDoc: (doc:Doc, dv:DocumentView) => doc._freeform_snapLines = !doc._freeform_snapLines, + checkResult: (doc: Doc) => BoolCast(doc?._freeform_snapLines, false), + setDoc: (doc: Doc, dv: DocumentView) => doc._freeform_snapLines = !doc._freeform_snapLines, }], ['viewAll', { - checkResult: (doc:Doc) => BoolCast(doc?._freeform_fitContentsToBox, false), - setDoc: (doc:Doc,dv:DocumentView) => { + checkResult: (doc: Doc) => BoolCast(doc?._freeform_fitContentsToBox, false), + setDoc: (doc: Doc, dv: DocumentView) => { if (persist) doc._freeform_fitContentsToBox = !doc._freeform_fitContentsToBox; else if (doc._freeform_fitContentsToBox) doc._freeform_fitContentsToBox = undefined; else (dv.ComponentView as CollectionFreeFormView)?.fitContentOnce(); }, }], ['center', { - checkResult: (doc:Doc) => BoolCast(doc?._stacking_alignCenter, false), - setDoc: (doc:Doc,dv:DocumentView) => doc._stacking_alignCenter = !doc._stacking_alignCenter, + checkResult: (doc: Doc) => BoolCast(doc?._stacking_alignCenter, false), + setDoc: (doc: Doc, dv: DocumentView) => doc._stacking_alignCenter = !doc._stacking_alignCenter, }], ['clusters', { - waitForRender: true, // flags that undo batch should terminate after a re-render giving the script the chance to fire - checkResult: (doc:Doc) => BoolCast(doc?._freeform_useClusters, false), - setDoc: (doc:Doc,dv:DocumentView) => doc._freeform_useClusters = !doc._freeform_useClusters, + waitForRender: true, + checkResult: (doc: Doc) => BoolCast(doc?._freeform_useClusters, false), + setDoc: (doc: Doc, dv: DocumentView) => doc._freeform_useClusters = !doc._freeform_useClusters, }], ['flashcards', { - checkResult: (doc:Doc) => BoolCast(Doc.UserDoc().defaultToFlashcards, false), - setDoc: (doc:Doc,dv:DocumentView) => Doc.UserDoc().defaultToFlashcards = !Doc.UserDoc().defaultToFlashcards, + checkResult: (doc: Doc) => BoolCast(Doc.UserDoc().defaultToFlashcards, false), + setDoc: (doc: Doc, dv: DocumentView) => Doc.UserDoc().defaultToFlashcards = !Doc.UserDoc().defaultToFlashcards, }], ['time', { - checkResult: (doc:Doc) => StrCast(doc?.cardSort), - setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "time", + checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "time", + setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort = "time", }], ['docType', { - checkResult: (doc:Doc) => StrCast(doc?.cardSort), - setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "type", + checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "type", + setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort = "type", }], ['color', { - checkResult: (doc:Doc) => StrCast(doc?.cardSort), - setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "color", + checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "color", + setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort = "color", }], - ['links', { - checkResult: (doc:Doc) => StrCast(doc?.cardSort), - setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "links", + checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "links", + setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort = "links", }], - ['custom1', { - checkResult: (doc:Doc) => StrCast(doc?.cardSort), - setDoc: (doc, dv) => { + checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "custom" && NumCast(doc?.customSortNumber) === 0, + setDoc: (doc: Doc, dv: DocumentView) => { doc.cardSort = "custom"; doc.customSortNumber = 0; + doc.visibleGroupNumbers = new List(); } - }], - + }], ['custom2', { - checkResult: (doc:Doc) => StrCast(doc?.cardSort), - setDoc: (doc, dv) => { + checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "custom" && NumCast(doc?.customSortNumber) === 1, + setDoc: (doc: Doc, dv: DocumentView) => { doc.cardSort = "custom"; doc.customSortNumber = 1; - console.log(doc.customSortNumber + " numberrrrrrrr") - } }], - + doc.visibleGroupNumbers = new List(); + } + }], ['custom3', { - checkResult: (doc:Doc) => StrCast(doc?.cardSort), - setDoc: (doc, dv) => { + checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "custom" && NumCast(doc?.customSortNumber) === 2, + setDoc: (doc: Doc, dv: DocumentView) => { doc.cardSort = "custom"; doc.customSortNumber = 2; - } }], + doc.visibleGroupNumbers = new List(); + } + }], ['chat', { - checkResult: (doc:Doc) => StrCast(doc?.cardSort), - setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "chat", + checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "chat", + setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort = "chat", + }], + ['1', { + checkResult: (doc: Doc) => NumListCast(doc?.visibleGroupNumbers).includes(0), + setDoc: (doc: Doc, dv: DocumentView) => { + let list = NumListCast(doc.visibleGroupNumbers); + if (list.includes(0)) { + let newList = new List(list.filter(d => d !== 0)); + doc.visibleGroupNumbers = newList; + } else { + list.push(0); + doc.visibleGroupNumbers = new List(list); + } + } }], - ]); + ['2', { + checkResult: (doc: Doc) => NumListCast(doc?.visibleGroupNumbers).includes(1), + setDoc: (doc: Doc, dv: DocumentView) => { + let list = NumListCast(doc.visibleGroupNumbers); + if (list.includes(1)) { + let newList = new List(list.filter(d => d !== 1)); + doc.visibleGroupNumbers = newList; + } else { + list.push(1); + doc.visibleGroupNumbers = new List(list); + } + } + }], + ['3', { + checkResult: (doc: Doc) => NumListCast(doc?.visibleGroupNumbers).includes(2), + setDoc: (doc: Doc, dv: DocumentView) => { + let list = NumListCast(doc.visibleGroupNumbers); + if (list.includes(2)) { + let newList = new List(list.filter(d => d !== 2)); + doc.visibleGroupNumbers = newList; + } else { + list.push(2); + doc.visibleGroupNumbers = new List(list); + } + } + }], + ['4', { + checkResult: (doc: Doc) => NumListCast(doc?.visibleGroupNumbers).includes(3), + setDoc: (doc: Doc, dv: DocumentView) => { + let list = NumListCast(doc.visibleGroupNumbers); + if (list.includes(3)) { + let newList = new List(list.filter(d => d !== 3)); + doc.visibleGroupNumbers = newList; + } else { + list.push(3); + doc.visibleGroupNumbers = new List(list); + } + } + }], + ]); if (checkResult) { return map.get(attr)?.checkResult(selected); @@ -194,6 +257,14 @@ ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid setTimeout(() => batch.end(), 100); }); + +ScriptingGlobals.add(function isThatCardGroup(n: number){ + const canvas = SelectionManager.Docs.lastElement(); + return canvas.customSortNumber == n + + +}) + // ScriptingGlobals.add(function setCardSortAttr(attr: 'time' | 'docType' | 'color', value: any, checkResult?: boolean) { // // const editorView = RichTextMenu.Instance?.TextView?.EditorView; // const selected = SelectionManager.Docs.lastElement(); diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss index 4425cf158..551c8e5af 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.scss +++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss @@ -55,10 +55,10 @@ $highlightedText: #82e0ff; overflow-y: auto; } - .btns-wrapper { + .btns-wrapper-gpt { height: 50px; display: flex; - justify-content: space-between; + justify-content: center; align-items: center; transform: translateY(30px); @@ -76,6 +76,8 @@ $highlightedText: #82e0ff; display: flex; align-items: center; } + + } button { diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index faf66af85..d94be1860 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -154,6 +154,13 @@ export class GPTPopup extends ObservableReactComponent { } @observable onSortComplete?: (sortResult: string) => void; + @observable cardsDoneLoading = false; + + @action setCardsDoneLoading(done: boolean) { + console.log(done + "HI HIHI") + this.cardsDoneLoading = done; + } + @@ -169,12 +176,14 @@ export class GPTPopup extends ObservableReactComponent { this.setSortDone(false); try { - const res = await gptAPICall(this.sortPrompt + this.sortDesc, GPTCallType.SORT); + + const res = await gptAPICall(this.sortDesc, GPTCallType.SORT); this.setSortText(res || 'Something went wrong :('); // Trigger the callback with the result if (this.onSortComplete) { this.onSortComplete(res || 'Something went wrong :('); + console.log(res) } } catch (err) { console.error(err); @@ -313,52 +322,63 @@ export class GPTPopup extends ObservableReactComponent { {this.heading("SORTING")} {this.loading ? (
-
- - Loading... -
-
- ) : !this.sortDone && ( -
- { - if (e.key === 'Enter') { - this.generateSort(); - } - e.stopPropagation(); - }} - type="text" - placeholder="Enter sorting instructions..." - id="search-input" - className="searchBox-input" - style={{ width: "100%" }} - /> +
+ + Loading...
- )} +
+ ) : ( + <> + {!this.cardsDoneLoading ? ( +
+
+ + Reading Cards... +
+
+ ) : ( + !this.sortDone && ( +
+
+ ) + )} - {this.sortDone && ( -
-
- -

{this.text== "Something went wrong :(" ? "Something went wrong :(" : "Sorting done! Feel free to move things around / regenerate :) !"}

- - - this.setSortDone(false)} - icon={} - color={StrCast(Doc.UserDoc().userVariantColor)} - /> - -
-
+ {this.sortDone && ( +
+
+

+ {this.text === "Something went wrong :(" ? + "Something went wrong :(" : + "Sorting done! Feel free to move things around / regenerate :) !"} +

+ this.setSortDone(false)} + icon={} + color={StrCast(Doc.UserDoc().userVariantColor)} + /> +
+
+ )} + )}
); + -- cgit v1.2.3-70-g09d2 From 0b32642fc9c2d33810252cd90e45b8b505ef49b2 Mon Sep 17 00:00:00 2001 From: aidahosa1 Date: Sat, 18 May 2024 00:16:31 -0400 Subject: done :) --- src/client/documents/Documents.ts | 14 +- .../views/collections/CollectionCardDeckView.tsx | 188 +++++++++++---------- 2 files changed, 112 insertions(+), 90 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index eded2b485..80774f4ad 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -501,8 +501,20 @@ export class DocumentOptions { cardSort?: STRt = new StrInfo('way cards are sorted in deck view'); customSortNumber?: NUMt = new NumInfo('number of custom sorts the user has created'); - customHashMap?: List + // customGroup1?: List + // customGroup2?: List + // customGroup3?: List visibleGroupNumbers?: List + custom1Group?: NUMt = new NumInfo('Which group a card is in for the 1st custom grouping'); + custom2Group?: NUMt = new NumInfo('Which group a card is in for the 2nd custom grouping'); + custom3Group?: NUMt = new NumInfo('Which group a card is in for the 3rd custom grouping'); + chatGroup?: NUMt = new NumInfo("Which group a card is in for the chat's grouping"); + chatAmGroups?: NUMt = new NumInfo("Number of cards created by chat sort"); + + + + + // card_sort_time?: BOOLt = new BoolInfo('whether sorting cards in deck view by time'); // card_sort_type?: BOOLt = new BoolInfo('whether sorting cards in deck view by type'); diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index 42c82ca74..c719c9e19 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -33,7 +33,8 @@ export class CollectionCardView extends CollectionSubView() { // documents themselves @observable hoveredNodeIndex = -1; - //index to group + + //key is the index in the child pair list, value is the id# for the group its in @observable customGroupDictionary: Map[] = [new Map(), new Map(), new Map()]; @@ -43,9 +44,9 @@ export class CollectionCardView extends CollectionSubView() { // console.log("Active Groups:", activeGroups); - for (let i=0; i< activeGroups.length; i++){ - console.log("Active Groups" + activeGroups[i]) - } + // for (let i=0; i< activeGroups.length; i++){ + // console.log("Active Groups" + activeGroups[i]) + // } // console.log("Current Custom Sort Number:", currCustom); if (activeGroups.length <= 0) { @@ -121,10 +122,32 @@ export class CollectionCardView extends CollectionSubView() { super(props); makeObservable(this); // this.rotationDegree(7); + const pairs = this.childLayoutPairs.filter(d => d.layout.type != DocumentType.LINK); + - if (this._props.Document.customHashMap != undefined){ - this.customGroupDictionary = this.getCustoms(StrListCast(this._props.Document.customHashMap)) + for (let i=0; i< pairs.length; i++){ + if (pairs[i].layout.custom1Group != undefined){ + this.customGroupDictionary[0].set(i, NumCast(pairs[i].layout.custom1Group)) + } + + if (pairs[i].layout.custom2Group != undefined){ + this.customGroupDictionary[1].set(i, NumCast(pairs[i].layout.custom2Group)) + } + + if (pairs[i].layout.custom3Group != undefined){ + this.customGroupDictionary[2].set(i, NumCast(pairs[i].layout.custom3Group)) + } + + if (pairs[i].layout.chatGroup != undefined){ + this.gptGroups.set(pairs[i].layout, NumCast(pairs[i].layout.chatGroup)) + this.amGPTGroups = NumCast(this._props.Document.chatAmGroups) + + } } + + // if (this._props.Document.customHashMap != undefined){ + // this.customGroupDictionary = this.convertListsToArrayOfMaps(DocListCast(this._props.Document.customGroup1),DocListCast(this._props.Document.customGroup2), DocListCast(this._props.Document.customGroup3)) + // } // this._props.Document.visibleGroupNumbers = new List([1,2,3,4]) @@ -139,32 +162,52 @@ export class CollectionCardView extends CollectionSubView() { ); } + //converts the customGroupDictionary into a Lists of Docs + mapToField(groupNumber: number) { + const groupList = new List(); + + const pairs = this.childLayoutPairs.filter(d => d.layout.type != DocumentType.LINK); + console.log(groupNumber+ "group#") + + this.customGroupDictionary[groupNumber].forEach((group, index) => { + const doc = pairs[index].layout; + groupList.push(doc); + }); + + this._props.Document[`group${groupNumber}`] = groupList; + } - @computed get mapToField(): List { - const resultList = new List(); - - this.customGroupDictionary.forEach(map => { - // Convert each map to a single string with entries formatted as "key:value" - const mapString = Array.from(map.entries()) - .map(([key, value]) => `${key}:${value}`) - .join(','); // Join all key-value pairs with commas - resultList.push(mapString); // Add the formatted string of the current map to the list + // This function converts three Lists of Docs back into an array of maps + convertListsToArrayOfMaps = (group0: Doc[], group1: Doc[], group2: Doc[]): Map[] => { + const mapsArray: Map[] = [ + new Map(), + new Map(), + new Map() + ]; + const layoutPairs = this.childLayoutPairs.filter(d => d.layout.type != DocumentType.LINK) + + group0.forEach((doc, index) => { + const docIndex = layoutPairs.findIndex(pair => pair.layout === doc); + if (docIndex !== -1) { + mapsArray[0].set(docIndex, 0); + } }); - return resultList; - } + group1.forEach((doc, index) => { + const docIndex = layoutPairs.findIndex(pair => pair.layout === doc); + if (docIndex !== -1) { + mapsArray[1].set(docIndex, 1); + } + }); - getCustoms = (stringFrom: string[]): Map[] => { - return stringFrom.map(s => { - // Create a new Map object for each string. - const map = new Map(); - // Split the string by commas to get key-value pairs, then process each pair. - s.split(',').forEach(pair => { - const [key, value] = pair.split(':'); - map.set(Number(key), Number(value)); - }); - return map; + group2.forEach((doc, index) => { + const docIndex = layoutPairs.findIndex(pair => pair.layout === doc); + if (docIndex !== -1) { + mapsArray[2].set(docIndex, 2); + } }); + + return mapsArray; } @@ -278,17 +321,6 @@ export class CollectionCardView extends CollectionSubView() { @computed get sortedDocsType() { - // if (this._props.Document.cardSort === 'chat'){ - // this.openChatPopup() - // const textDesc = await this.childPairStringList(); - // GPTPopup.Instance.setSortDesc(textDesc) - // } - - // if (this._props.Document.cardSort === 'chat' && this.sortedDocs.length === 0) { - - // this.smartSort(); // Trigger the sorting if it hasn't been done yet - // return { docs: [] }; // Return an empty array or a loading state - // } const desc = BoolCast(this.layoutDoc.sortDesc); @@ -374,9 +406,9 @@ export class CollectionCardView extends CollectionSubView() { // console.log(this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)]) typeA = this.gptGroups.get(docA.layout) ?? '9999'; - console.log(typeA + "typea") + // console.log(typeA + "typea") typeB = this.gptGroups.get(docB.layout) ?? '9999'; - console.log(typeB + "typeB") + // console.log(typeB + "typeB") // console.log(typeA + "A") @@ -539,35 +571,45 @@ export class CollectionCardView extends CollectionSubView() { onMouseEnter={() => this.setHoveredNodeIndex(index)}> {this.displayDoc(childPair, childScreenToLocal)} - {this._props.Document.cardSort == 'custom' ? this.renderButtons(childPairIndex) : ''} + {this._props.Document.cardSort == 'custom' ? this.renderButtons(childPairIndex, childPair.layout, false) : ''} {this._props.Document.cardSort == 'chat' ? this.renderButtons(childPairIndex, childPair.layout, true) : ''} ); }); } + - - - - - - - @action toggleButton(childPairIndex: number, buttonID: number, isChat = false, doc?: Doc) { + @action toggleButton(childPairIndex: number, buttonID: number, isChat = false, doc: Doc) { if (!isChat) { - this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].set(childPairIndex, buttonID); - this._props.Document.customHashMap = this.mapToField; + const sortNumber = NumCast(this._props.Document.customSortNumber) + this.customGroupDictionary[sortNumber].set(childPairIndex, buttonID) + switch (sortNumber){ + case 0: + doc.custom1Group = buttonID + break + case 1: + doc.custom2Group = buttonID + break + case 2: + doc.custom3Group = buttonID + break + default: + break + } + // this.mapToField(buttonID); } if (isChat && doc) { this.gptGroups.set(doc, buttonID); + doc.chatGroup = buttonID } // console.log(`Toggled button for childPairIndex: ${childPairIndex}, buttonID: ${buttonID}, isChat: ${isChat}`); // console.log(`Updated customGroupDictionary:`, this.customGroupDictionary); - if (isChat && doc) { - console.log(`Updated gptGroups for doc ${doc}:`, this.gptGroups.get(doc)); - } + // if (isChat && doc) { + // // console.log(`Updated gptGroups for doc ${doc}:`, this.gptGroups.get(doc)); + // } } @@ -645,39 +687,6 @@ export class CollectionCardView extends CollectionSubView() { } } - - - //a set of all the images that have already been given desc - //a map from the text to the Doc (for everything) - - - // @action async smartSort() { - // this.isLoading = true; - // console.log("loading"); - - // // Store the result of childPairStringList in a variable - // const childPairStrings = await this.childPairStringList(); - - // if (childPairStrings === "") { - // console.log("no child pairs :("); - // } else { - // console.log(childPairStrings + " og list"); - // let prompt = childPairStrings; - - // let res = await gptAPICall(prompt, GPTCallType.SORT); - // this.isLoading = false; - // if (res == 'Error connecting with API.') { - // // If GPT call failed - // console.error('GPT call failed'); - // } else if (res != null) { - // console.log(res); - // this.processGptOutput(res); - // // Update the observable with the sorted documents - // this.sortedDocs = this.sort(this.myChildLayoutPairs, 'gpt', BoolCast(this.layoutDoc.sortDesc)).docs; - // } - // this.isLoading = false; - // } - // } gptGroups = new ObservableMap @observable amGPTGroups = 0 @@ -688,6 +697,7 @@ export class CollectionCardView extends CollectionSubView() { // Split the string into individual list items const listItems = gptOutput.split('======').filter(item => item.trim() !== ''); this.amGPTGroups = listItems.length + this._props.Document.chatAmGroups = this.amGPTGroups listItems.forEach((item, index) => { // Split the item by '~~~~~~' to get all descriptors @@ -730,7 +740,7 @@ export class CollectionCardView extends CollectionSubView() { - renderButtons(childPairIndex: number, doc?: Doc, isChat = false) { + renderButtons(childPairIndex: number, doc: Doc, isChat = false) { const buttons = []; // Array to hold the button elements const groupNumber = NumCast(this._props.Document.customSortNumber); @@ -744,11 +754,11 @@ export class CollectionCardView extends CollectionSubView() { activeButtonIndex = this.gptGroups.get(doc); } - console.log("childPairIndex:", childPairIndex, "activeButtonIndex:", activeButtonIndex, "groupNumber:", groupNumber); + // console.log("childPairIndex:", childPairIndex, "activeButtonIndex:", activeButtonIndex, "groupNumber:", groupNumber); for (let i = 0; i < amButtons; i++) { const isActive = activeButtonIndex === i; - console.log(`Rendering button ${i} for childPairIndex ${childPairIndex} isActive: ${isActive}`); + // console.log(`Rendering button ${i} for childPairIndex ${childPairIndex} isActive: ${isActive}`); buttons.push(