aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormonoguitari <113245090+monoguitari@users.noreply.github.com>2023-08-24 14:35:33 -0400
committermonoguitari <113245090+monoguitari@users.noreply.github.com>2023-08-24 14:35:33 -0400
commit5276e521260ffe8b450a63aba20273f8e628e7fc (patch)
tree708e9690632302e6d806ee6659a40f0ff6353663
parent6297cd58741f39ce7f6510e0a4cc634d62d4778e (diff)
parent20e3d33d864f9ee9db2ca65848b0f42a087b699e (diff)
Merge branch 'master' into advanced-trails-2-jesus
-rw-r--r--package-lock.json60
-rw-r--r--package.json3
-rw-r--r--report.20230313.165455.65490.0.001.json1294
-rw-r--r--src/client/apis/gpt/GPT.ts29
-rw-r--r--src/client/util/CurrentUserUtils.ts14
-rw-r--r--src/client/util/DragManager.ts4
-rw-r--r--src/client/util/RTFMarkup.tsx2
-rw-r--r--src/client/util/SettingsManager.tsx9
-rw-r--r--src/client/views/DashboardView.tsx10
-rw-r--r--src/client/views/MainView.tsx11
-rw-r--r--src/client/views/OverlayView.scss1
-rw-r--r--src/client/views/OverlayView.tsx5
-rw-r--r--src/client/views/StyleProvider.tsx9
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx4
-rw-r--r--src/client/views/collections/CollectionSubView.tsx6
-rw-r--r--src/client/views/collections/TabDocView.tsx6
-rw-r--r--src/client/views/collections/TreeView.tsx27
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx15
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx5
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx19
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss6
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx7
-rw-r--r--src/client/views/global/globalScripts.ts8
-rw-r--r--src/client/views/nodes/DocumentView.tsx2
-rw-r--r--src/client/views/nodes/FontIconBox/FontIconBox.tsx9
-rw-r--r--src/client/views/nodes/ImageBox.tsx23
-rw-r--r--src/client/views/nodes/WebBoxRenderer.js6
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx59
-rw-r--r--src/client/views/nodes/generativeFill/GenerativeFill.scss97
-rw-r--r--src/client/views/nodes/generativeFill/GenerativeFill.tsx584
-rw-r--r--src/client/views/nodes/generativeFill/GenerativeFillButtons.scss4
-rw-r--r--src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx42
-rw-r--r--src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts25
-rw-r--r--src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts10
-rw-r--r--src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts286
-rw-r--r--src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts15
-rw-r--r--src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts9
-rw-r--r--src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillInterfaces.ts20
-rw-r--r--src/client/views/pdf/AnchorMenu.tsx103
-rw-r--r--src/client/views/pdf/GPTPopup/GPTPopup.scss66
-rw-r--r--src/client/views/pdf/GPTPopup/GPTPopup.tsx254
-rw-r--r--src/fields/DocSymbols.ts2
-rw-r--r--src/server/ApiManagers/AzureManager.ts19
-rw-r--r--src/server/DashUploadUtils.ts17
44 files changed, 1595 insertions, 1611 deletions
diff --git a/package-lock.json b/package-lock.json
index e6985cf65..31a29409b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6507,9 +6507,9 @@
}
},
"browndash-components": {
- "version": "0.0.97",
- "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.0.97.tgz",
- "integrity": "sha512-UO8NQrOJoAq+UQGR8TXCRzr6jX9qSnEot9FHP7zdwlKH1sGntbQpyM5BXdwfkyXo+oh1qstTGkyR4s20CY7Yrw==",
+ "version": "0.0.92",
+ "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.0.92.tgz",
+ "integrity": "sha512-eE/6WQNZiLnaXUKyoaMm0PDYjExUsFJ9VTAIIxROpYPosIBKWNZ743xaOfmehib5us9hEXJb0CvUFJQb8rzDVw==",
"requires": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
@@ -24343,6 +24343,55 @@
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
},
+ "react-loader-spinner": {
+ "version": "5.3.4",
+ "resolved": "https://registry.npmjs.org/react-loader-spinner/-/react-loader-spinner-5.3.4.tgz",
+ "integrity": "sha512-G2vw4ssX+RDZ/vfaeva06yfNqyFViv/u+tVZ3kFLy5TKNlNx2DbuwreBSpRtPespQA+VxinxUJsigwLwG9erOg==",
+ "requires": {
+ "react-is": "^18.2.0",
+ "styled-components": "^5.3.5",
+ "styled-tools": "^1.7.2"
+ },
+ "dependencies": {
+ "css-to-react-native": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
+ "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
+ "requires": {
+ "camelize": "^1.0.0",
+ "css-color-keywords": "^1.0.0",
+ "postcss-value-parser": "^4.0.2"
+ }
+ },
+ "postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
+ },
+ "react-is": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
+ },
+ "styled-components": {
+ "version": "5.3.11",
+ "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.11.tgz",
+ "integrity": "sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==",
+ "requires": {
+ "@babel/helper-module-imports": "^7.0.0",
+ "@babel/traverse": "^7.4.5",
+ "@emotion/is-prop-valid": "^1.1.0",
+ "@emotion/stylis": "^0.8.4",
+ "@emotion/unitless": "^0.7.4",
+ "babel-plugin-styled-components": ">= 1.12.0",
+ "css-to-react-native": "^3.0.0",
+ "hoist-non-react-statics": "^3.0.0",
+ "shallowequal": "^1.1.0",
+ "supports-color": "^5.5.0"
+ }
+ }
+ }
+ },
"react-loading": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/react-loading/-/react-loading-2.0.3.tgz",
@@ -26749,6 +26798,11 @@
}
}
},
+ "styled-tools": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/styled-tools/-/styled-tools-1.7.2.tgz",
+ "integrity": "sha512-IjLxzM20RMwAsx8M1QoRlCG/Kmq8lKzCGyospjtSXt/BTIIcvgTonaxQAsKnBrsZNwhpHzO9ADx5te0h76ILVg=="
+ },
"stylis": {
"version": "3.5.4",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-3.5.4.tgz",
diff --git a/package.json b/package.json
index ea4e6a5e6..a1e7f858a 100644
--- a/package.json
+++ b/package.json
@@ -14,7 +14,7 @@
"start-release-debug": "cross-env RELEASE=true USE_AZURE=true NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev --inspect -- src/server/index.ts",
"start": "cross-env NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev --debug --transpile-only -- src/server/index.ts",
"oldstart": "cross-env NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev --debug -- src/server/index.ts",
- "debug": "cross-env USE_AZURE=false NODE_OPTIONS=--max_old_space_size=8192 ts-node-dev --transpile-only --inspect -- src/server/index.ts",
+ "debug": "cross-env USE_AZURE=true NODE_OPTIONS=--max_old_space_size=8192 ts-node-dev --transpile-only --inspect -- src/server/index.ts",
"monitor": "cross-env MONITORED=true NODE_OPTIONS=--max_old_space_size=4096 ts-node src/server/index.ts",
"build": "cross-env NODE_OPTIONS=--max_old_space_size=8192 webpack --env production",
"test": "mocha -r ts-node/register test/**/*.ts",
@@ -294,6 +294,7 @@
"react-grid-layout": "^1.3.4",
"react-icons": "^4.3.1",
"react-jsx-parser": "^1.29.0",
+ "react-loader-spinner": "^5.3.4",
"react-loading": "^2.0.3",
"react-markdown": "^8.0.3",
"react-measure": "^2.5.2",
diff --git a/report.20230313.165455.65490.0.001.json b/report.20230313.165455.65490.0.001.json
deleted file mode 100644
index 689fcf9eb..000000000
--- a/report.20230313.165455.65490.0.001.json
+++ /dev/null
@@ -1,1294 +0,0 @@
-
-{
- "header": {
- "reportVersion": 1,
- "event": "Allocation failed - JavaScript heap out of memory",
- "trigger": "FatalError",
- "filename": "report.20230313.165455.65490.0.001.json",
- "dumpEventTime": "2023-03-13T16:54:55Z",
- "dumpEventTimeStamp": "1678740895169",
- "processId": 65490,
- "cwd": "/Users/sarah/Desktop/dash/Dash-Web",
- "commandLine": [
- "/Users/sarah/.nvm/versions/node/v12.16.0/bin/node",
- "--max-old-space-size=2048",
- "/Users/sarah/Desktop/dash/Dash-Web/node_modules/ts-node-dev/lib/wrap.js",
- "/Users/sarah/Desktop/dash/Dash-Web/node_modules/fork-ts-checker-webpack-plugin/lib/service.js"
- ],
- "nodejsVersion": "v12.16.0",
- "wordSize": 64,
- "arch": "x64",
- "platform": "darwin",
- "componentVersions": {
- "node": "12.16.0",
- "v8": "7.8.279.23-node.31",
- "uv": "1.34.0",
- "zlib": "1.2.11",
- "brotli": "1.0.7",
- "ares": "1.15.0",
- "modules": "72",
- "nghttp2": "1.40.0",
- "napi": "5",
- "llhttp": "2.0.4",
- "http_parser": "2.9.3",
- "openssl": "1.1.1d",
- "cldr": "35.1",
- "icu": "64.2",
- "tz": "2019c",
- "unicode": "12.1"
- },
- "release": {
- "name": "node",
- "lts": "Erbium",
- "headersUrl": "https://nodejs.org/download/release/v12.16.0/node-v12.16.0-headers.tar.gz",
- "sourceUrl": "https://nodejs.org/download/release/v12.16.0/node-v12.16.0.tar.gz"
- },
- "osName": "Darwin",
- "osRelease": "20.4.0",
- "osVersion": "Darwin Kernel Version 20.4.0: Fri Mar 5 01:14:14 PST 2021; root:xnu-7195.101.1~3/RELEASE_X86_64",
- "osMachine": "x86_64",
- "cpus": [
- {
- "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
- "speed": 2300,
- "user": 95972310,
- "nice": 0,
- "sys": 69742110,
- "idle": 1132989220,
- "irq": 0
- },
- {
- "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
- "speed": 2300,
- "user": 1235990,
- "nice": 0,
- "sys": 1442360,
- "idle": 1293767630,
- "irq": 0
- },
- {
- "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
- "speed": 2300,
- "user": 77301330,
- "nice": 0,
- "sys": 43426390,
- "idle": 1175757970,
- "irq": 0
- },
- {
- "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
- "speed": 2300,
- "user": 1270620,
- "nice": 0,
- "sys": 1351190,
- "idle": 1293823030,
- "irq": 0
- },
- {
- "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
- "speed": 2300,
- "user": 56966210,
- "nice": 0,
- "sys": 28908780,
- "idle": 1210608910,
- "irq": 0
- },
- {
- "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
- "speed": 2300,
- "user": 1291530,
- "nice": 0,
- "sys": 1265830,
- "idle": 1293886140,
- "irq": 0
- },
- {
- "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
- "speed": 2300,
- "user": 46074390,
- "nice": 0,
- "sys": 22963990,
- "idle": 1227443730,
- "irq": 0
- },
- {
- "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
- "speed": 2300,
- "user": 1311400,
- "nice": 0,
- "sys": 1200930,
- "idle": 1293929770,
- "irq": 0
- },
- {
- "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
- "speed": 2300,
- "user": 36059780,
- "nice": 0,
- "sys": 17309680,
- "idle": 1243110810,
- "irq": 0
- },
- {
- "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
- "speed": 2300,
- "user": 1303400,
- "nice": 0,
- "sys": 1134770,
- "idle": 1294002540,
- "irq": 0
- },
- {
- "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
- "speed": 2300,
- "user": 28665300,
- "nice": 0,
- "sys": 12769180,
- "idle": 1255043860,
- "irq": 0
- },
- {
- "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
- "speed": 2300,
- "user": 1287310,
- "nice": 0,
- "sys": 1073720,
- "idle": 1294078280,
- "irq": 0
- },
- {
- "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
- "speed": 2300,
- "user": 22112060,
- "nice": 0,
- "sys": 9084160,
- "idle": 1265280170,
- "irq": 0
- },
- {
- "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
- "speed": 2300,
- "user": 1256320,
- "nice": 0,
- "sys": 1011250,
- "idle": 1294170360,
- "irq": 0
- },
- {
- "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
- "speed": 2300,
- "user": 17120610,
- "nice": 0,
- "sys": 6395650,
- "idle": 1272958030,
- "irq": 0
- },
- {
- "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
- "speed": 2300,
- "user": 1227540,
- "nice": 0,
- "sys": 963060,
- "idle": 1294245890,
- "irq": 0
- }
- ],
- "networkInterfaces": [
- {
- "name": "lo0",
- "internal": true,
- "mac": "00:00:00:00:00:00",
- "address": "127.0.0.1",
- "netmask": "255.0.0.0",
- "family": "IPv4"
- },
- {
- "name": "lo0",
- "internal": true,
- "mac": "00:00:00:00:00:00",
- "address": "::1",
- "netmask": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
- "family": "IPv6",
- "scopeid": 0
- },
- {
- "name": "lo0",
- "internal": true,
- "mac": "00:00:00:00:00:00",
- "address": "fe80::1",
- "netmask": "ffff:ffff:ffff:ffff::",
- "family": "IPv6",
- "scopeid": 1
- },
- {
- "name": "en0",
- "internal": false,
- "mac": "88:66:5a:29:28:77",
- "address": "fe80::cc1:cf3b:afa2:144f",
- "netmask": "ffff:ffff:ffff:ffff::",
- "family": "IPv6",
- "scopeid": 6
- },
- {
- "name": "en0",
- "internal": false,
- "mac": "88:66:5a:29:28:77",
- "address": "2620:6e:6000:3100:148e:201a:1a33:145d",
- "netmask": "ffff:ffff:ffff:ffff::",
- "family": "IPv6",
- "scopeid": 0
- },
- {
- "name": "en0",
- "internal": false,
- "mac": "88:66:5a:29:28:77",
- "address": "2620:6e:6000:3100:31e4:88bf:1195:6926",
- "netmask": "ffff:ffff:ffff:ffff::",
- "family": "IPv6",
- "scopeid": 0
- },
- {
- "name": "en0",
- "internal": false,
- "mac": "88:66:5a:29:28:77",
- "address": "fdac:89a:4f49:41ac:83d:26cd:abc5:e973",
- "netmask": "ffff:ffff:ffff:ffff::",
- "family": "IPv6",
- "scopeid": 0
- },
- {
- "name": "en0",
- "internal": false,
- "mac": "88:66:5a:29:28:77",
- "address": "fdac:89a:4f49:41ac:15b2:4a9e:88a9:34c3",
- "netmask": "ffff:ffff:ffff:ffff::",
- "family": "IPv6",
- "scopeid": 0
- },
- {
- "name": "en0",
- "internal": false,
- "mac": "88:66:5a:29:28:77",
- "address": "10.38.53.246",
- "netmask": "255.255.192.0",
- "family": "IPv4"
- },
- {
- "name": "en0",
- "internal": false,
- "mac": "88:66:5a:29:28:77",
- "address": "fd8c:23f:4de7:4523:cc7:18bd:a001:a86b",
- "netmask": "ffff:ffff:ffff:ffff::",
- "family": "IPv6",
- "scopeid": 0
- },
- {
- "name": "en0",
- "internal": false,
- "mac": "88:66:5a:29:28:77",
- "address": "fd8c:23f:4de7:4523:6cbc:dfb0:da3f:fa06",
- "netmask": "ffff:ffff:ffff:ffff::",
- "family": "IPv6",
- "scopeid": 0
- },
- {
- "name": "en0",
- "internal": false,
- "mac": "88:66:5a:29:28:77",
- "address": "fdc9:5cae:17e4:4c54:c1e:4892:ccc4:93ae",
- "netmask": "ffff:ffff:ffff:ffff::",
- "family": "IPv6",
- "scopeid": 0
- },
- {
- "name": "en0",
- "internal": false,
- "mac": "88:66:5a:29:28:77",
- "address": "fdc9:5cae:17e4:4c54:307f:315d:52f7:511f",
- "netmask": "ffff:ffff:ffff:ffff::",
- "family": "IPv6",
- "scopeid": 0
- },
- {
- "name": "en0",
- "internal": false,
- "mac": "88:66:5a:29:28:77",
- "address": "fdd3:9b36:2480:4c6f:cff:ec25:4ee6:54c",
- "netmask": "ffff:ffff:ffff:ffff::",
- "family": "IPv6",
- "scopeid": 0
- },
- {
- "name": "en0",
- "internal": false,
- "mac": "88:66:5a:29:28:77",
- "address": "fdd3:9b36:2480:4c6f:9dba:7458:3932:be33",
- "netmask": "ffff:ffff:ffff:ffff::",
- "family": "IPv6",
- "scopeid": 0
- },
- {
- "name": "awdl0",
- "internal": false,
- "mac": "a6:20:c0:51:e8:8f",
- "address": "fe80::a420:c0ff:fe51:e88f",
- "netmask": "ffff:ffff:ffff:ffff::",
- "family": "IPv6",
- "scopeid": 12
- },
- {
- "name": "llw0",
- "internal": false,
- "mac": "a6:20:c0:51:e8:8f",
- "address": "fe80::a420:c0ff:fe51:e88f",
- "netmask": "ffff:ffff:ffff:ffff::",
- "family": "IPv6",
- "scopeid": 13
- },
- {
- "name": "utun0",
- "internal": false,
- "mac": "00:00:00:00:00:00",
- "address": "fe80::15ac:b094:e48b:b227",
- "netmask": "ffff:ffff:ffff:ffff::",
- "family": "IPv6",
- "scopeid": 14
- },
- {
- "name": "utun1",
- "internal": false,
- "mac": "00:00:00:00:00:00",
- "address": "fe80::97ec:93db:83a3:75ce",
- "netmask": "ffff:ffff:ffff:ffff::",
- "family": "IPv6",
- "scopeid": 15
- },
- {
- "name": "en5",
- "internal": false,
- "mac": "ac:de:48:00:11:22",
- "address": "fe80::aede:48ff:fe00:1122",
- "netmask": "ffff:ffff:ffff:ffff::",
- "family": "IPv6",
- "scopeid": 5
- }
- ],
- "host": "sarahs-mbp.devices.brown.edu"
- },
- "javascriptStack": {
- "message": "No stack.",
- "stack": [
- "Unavailable."
- ]
- },
- "nativeStack": [
- {
- "pc": "0x000000010015c8ca",
- "symbol": "report::TriggerNodeReport(v8::Isolate*, node::Environment*, char const*, char const*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, v8::Local<v8::String>) [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
- },
- {
- "pc": "0x0000000100080f3e",
- "symbol": "node::OnFatalError(char const*, char const*) [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
- },
- {
- "pc": "0x0000000100185467",
- "symbol": "v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
- },
- {
- "pc": "0x0000000100185403",
- "symbol": "v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
- },
- {
- "pc": "0x000000010030b5f5",
- "symbol": "v8::internal::Heap::FatalProcessOutOfMemory(char const*) [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
- },
- {
- "pc": "0x000000010030ccc4",
- "symbol": "v8::internal::Heap::RecomputeLimits(v8::internal::GarbageCollector) [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
- },
- {
- "pc": "0x0000000100309b37",
- "symbol": "v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::GCCallbackFlags) [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
- },
- {
- "pc": "0x0000000100307afd",
- "symbol": "v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
- },
- {
- "pc": "0x00000001003132ba",
- "symbol": "v8::internal::Heap::AllocateRawWithLightRetry(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
- },
- {
- "pc": "0x0000000100313341",
- "symbol": "v8::internal::Heap::AllocateRawWithRetryOrFail(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
- },
- {
- "pc": "0x00000001002e065b",
- "symbol": "v8::internal::Factory::NewFillerObject(int, bool, v8::internal::AllocationType, v8::internal::AllocationOrigin) [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
- },
- {
- "pc": "0x0000000100618a18",
- "symbol": "v8::internal::Runtime_AllocateInYoungGeneration(int, unsigned long*, v8::internal::Isolate*) [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
- },
- {
- "pc": "0x0000000100950c19",
- "symbol": "Builtins_CEntry_Return1_DontSaveFPRegs_ArgvOnStack_NoBuiltinExit [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
- }
- ],
- "javascriptHeap": {
- "totalMemory": 2151882752,
- "totalCommittedMemory": 2149824464,
- "usedMemory": 2139828064,
- "availableMemory": 48165544,
- "memoryLimit": 2197815296,
- "heapSpaces": {
- "read_only_space": {
- "memorySize": 262144,
- "committedMemory": 33088,
- "capacity": 32808,
- "used": 32808,
- "available": 0
- },
- "new_space": {
- "memorySize": 2097152,
- "committedMemory": 1076448,
- "capacity": 1047456,
- "used": 28792,
- "available": 1018664
- },
- "old_space": {
- "memorySize": 1958100992,
- "committedMemory": 1957877576,
- "capacity": 1951020152,
- "used": 1950853272,
- "available": 166880
- },
- "code_space": {
- "memorySize": 15372288,
- "committedMemory": 14957376,
- "capacity": 13767968,
- "used": 13767968,
- "available": 0
- },
- "map_space": {
- "memorySize": 1576960,
- "committedMemory": 1406760,
- "capacity": 1236000,
- "used": 1236000,
- "available": 0
- },
- "large_object_space": {
- "memorySize": 174424064,
- "committedMemory": 174424064,
- "capacity": 173906440,
- "used": 173906440,
- "available": 0
- },
- "code_large_object_space": {
- "memorySize": 49152,
- "committedMemory": 49152,
- "capacity": 2784,
- "used": 2784,
- "available": 0
- },
- "new_large_object_space": {
- "memorySize": 0,
- "committedMemory": 0,
- "capacity": 1047456,
- "used": 0,
- "available": 1047456
- }
- }
- },
- "resourceUsage": {
- "userCpuSeconds": 302.098,
- "kernelCpuSeconds": 7.74276,
- "cpuConsumptionPercent": 10.3834,
- "maxRss": 2286298333184,
- "pageFaults": {
- "IORequired": 35,
- "IONotRequired": 2289110
- },
- "fsActivity": {
- "reads": 0,
- "writes": 0
- }
- },
- "libuv": [
- ],
- "environmentVariables": {
- "npm_config_save_dev": "",
- "npm_config_legacy_bundling": "",
- "npm_config_dry_run": "",
- "npm_package_dependencies_translate_google_api": "^1.0.4",
- "npm_package_dependencies_request": "^2.88.2",
- "npm_package_dependencies_express_flash": "0.0.2",
- "npm_package_dependencies__fortawesome_fontawesome_svg_core": "^1.3.0",
- "NVM_INC": "/Users/sarah/.nvm/versions/node/v12.16.0/include/node",
- "npm_config_viewer": "man",
- "npm_config_only": "",
- "npm_config_commit_hooks": "true",
- "npm_config_browser": "",
- "npm_package_gitHead": "4c2584baf8bae0cde714c832b0768d3c08864422",
- "npm_package_dependencies_webpack_dev_middleware": "^5.3.1",
- "npm_package_dependencies_webpack_cli": "^4.10.0",
- "npm_package_devDependencies_prettier": "^2.7.1",
- "npm_package_devDependencies_awesome_typescript_loader": "^5.2.1",
- "npm_package_devDependencies__types_archiver": "^3.1.1",
- "npm_config_also": "",
- "npm_package_dependencies_react_jsx_parser": "^1.29.0",
- "npm_package_dependencies_mongoose": "^5.13.14",
- "npm_package_dependencies_connect_flash": "^0.1.1",
- "npm_package_browser_child_process": "false",
- "npm_config_sign_git_commit": "",
- "npm_config_rollback": "true",
- "npm_package_dependencies_material_ui": "^0.20.2",
- "npm_package_devDependencies__types_sharp": "^0.23.1",
- "npm_package_devDependencies__types_passport_local": "^1.0.34",
- "npm_package_devDependencies__types_dotenv": "^6.1.1",
- "npm_package_devDependencies__types_cookie_parser": "^1.4.2",
- "TERM_PROGRAM": "Apple_Terminal",
- "NODE": "/Users/sarah/.nvm/versions/node/v12.16.0/bin/node",
- "npm_config_usage": "",
- "npm_config_audit": "true",
- "npm_package_dependencies_reveal_js": "^4.3.0",
- "npm_package_dependencies_process": "^0.11.10",
- "npm_package_dependencies_pdfjs": "^2.4.7",
- "npm_package_dependencies_html_to_image": "^0.1.3",
- "npm_package_devDependencies_file_loader": "^3.0.1",
- "npm_package_devDependencies__types_express_flash": "0.0.0",
- "npm_package_scripts_monitor": "cross-env MONITORED=true NODE_OPTIONS=--max_old_space_size=4096 ts-node src/server/index.ts",
- "INIT_CWD": "/Users/sarah/Desktop/dash/Dash-Web",
- "npm_package_dependencies_rehype_raw": "^6.1.1",
- "npm_package_dependencies_react_audio_waveform": "0.0.5",
- "npm_package_dependencies_path_browserify": "^1.0.1",
- "npm_package_dependencies_nodemailer": "^5.1.1",
- "npm_package_dependencies_axios": "^0.19.2",
- "npm_package_devDependencies_typescript": "^4.7.4",
- "NVM_CD_FLAGS": "-q",
- "npm_config_globalignorefile": "/Users/sarah/.nvm/versions/node/v12.16.0/etc/npmignore",
- "npm_package_dependencies_react_grid_layout": "^1.3.4",
- "npm_package_dependencies_prosemirror_find_replace": "^0.9.0",
- "npm_package_dependencies_normalize_css": "^8.0.1",
- "npm_package_devDependencies_mocha": "^5.2.0",
- "npm_package_devDependencies__types_express_session": "^1.17.5",
- "SHELL": "/bin/zsh",
- "TERM": "xterm-256color",
- "npm_config_shell": "/bin/zsh",
- "npm_config_maxsockets": "50",
- "npm_config_init_author_url": "",
- "npm_package_dependencies_prosemirror_dev_tools": "^3.1.0",
- "npm_package_dependencies_p_limit": "^2.2.0",
- "npm_package_dependencies_bson": "^4.6.1",
- "npm_package_dependencies__types_dom_speech_recognition": "0.0.1",
- "npm_package_devDependencies_style_loader": "^0.23.1",
- "npm_package_devDependencies__types_react_datepicker": "^3.1.8",
- "npm_config_shrinkwrap": "true",
- "npm_config_parseable": "",
- "npm_config_metrics_registry": "https://registry.npmjs.org/",
- "npm_package_dependencies_xregexp": "^4.4.1",
- "npm_package_dependencies_shelljs": "^0.8.5",
- "npm_package_dependencies_bezier_curve": "^1.0.0",
- "npm_package_devDependencies_tslint": "^5.20.1",
- "npm_package_devDependencies__types_react_transition_group": "^4.4.5",
- "npm_package_scripts_tsc": "tsc",
- "TMPDIR": "/var/folders/yk/p_39q8jn673c5p8_66mcxm7r0000gn/T/",
- "npm_config_timing": "",
- "npm_config_init_license": "ISC",
- "npm_package_dependencies_socket_io": "^2.5.0",
- "npm_package_dependencies_probe_image_size": "^4.0.0",
- "npm_package_dependencies_canvas": "^2.9.3",
- "npm_package_dependencies__hig_theme_data": "^2.23.1",
- "npm_package_devDependencies__types_react_select": "^3.1.2",
- "npm_package_devDependencies__types_prosemirror_model": "^1.16.1",
- "CONDA_SHLVL": "1",
- "npm_config_if_present": "",
- "npm_package_dependencies_typescript_collections": "^1.3.3",
- "npm_package_dependencies_rimraf": "^3.0.0",
- "npm_package_dependencies_react_autosuggest": "^9.4.3",
- "npm_package_dependencies_flexlayout_react": "^0.3.11",
- "npm_package_dependencies_find_in_files": "^0.5.0",
- "npm_package_devDependencies__types_chai": "^4.3.0",
- "CONDA_PROMPT_MODIFIER": "(base) ",
- "TERM_PROGRAM_VERSION": "440",
- "npm_package_dependencies_prosemirror_inputrules": "^1.1.3",
- "npm_package_dependencies_bcrypt_nodejs": "0.0.3",
- "npm_package_dependencies_async": "^2.6.2",
- "npm_config_sign_git_tag": "",
- "npm_config_init_author_email": "",
- "npm_config_cache_max": "Infinity",
- "npm_package_dependencies_uuid": "^3.4.0",
- "npm_package_dependencies_supercluster": "^7.1.4",
- "npm_package_dependencies_remark_gfm": "^3.0.1",
- "npm_package_dependencies_connect_mongo": "^2.0.3",
- "npm_package_dependencies_browser_assert": "^1.2.1",
- "npm_package_devDependencies_sass_loader": "^7.3.1",
- "npm_config_preid": "",
- "npm_config_long": "",
- "npm_config_local_address": "",
- "npm_config_git_tag_version": "true",
- "npm_config_cert": "",
- "npm_package_dependencies_js_datepicker": "^4.6.6",
- "npm_package_devDependencies__types_webpack_hot_middleware": "^2.25.6",
- "npm_package_devDependencies__types_mongodb": "^3.6.20",
- "npm_package_devDependencies__types_mocha": "^5.2.6",
- "TERM_SESSION_ID": "BF3A3D73-8B2D-4041-BAFA-CCC983EE3D05",
- "npm_config_registry": "https://registry.npmjs.org/",
- "npm_config_noproxy": "",
- "npm_config_fetch_retries": "2",
- "npm_package_dependencies_react_compound_slider": "^2.5.0",
- "npm_package_dependencies_prosemirror_history": "^1.2.0",
- "npm_package_devDependencies__types_react_color": "^2.17.6",
- "npm_package_devDependencies__types_google_maps_react": "^2.0.5",
- "npm_package_devDependencies__types_color": "^3.0.3",
- "npm_package_dependencies_react_dom": "^18.2.0",
- "npm_package_dependencies_passport_local": "^1.0.0",
- "npm_package_dependencies__octokit_core": "^4.0.4",
- "npm_package_devDependencies__types_async": "^2.4.1",
- "npm_package_scripts_debug": "cross-env NODE_OPTIONS=--max_old_space_size=8192 ts-node-dev --transpile-only --inspect -- src/server/index.ts",
- "npm_package_scripts_oldstart": "cross-env NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev --debug -- src/server/index.ts",
- "npm_config_versions": "",
- "npm_config_message": "%s",
- "npm_config_key": "",
- "npm_package_readmeFilename": "README.md",
- "npm_package_dependencies_react_refresh_typescript": "^2.0.7",
- "npm_package_dependencies_image_size": "^0.7.5",
- "npm_package_dependencies_html_to_text": "^5.1.1",
- "npm_package_dependencies_express_validator": "^5.3.1",
- "npm_package_devDependencies_eslint_plugin_jsx_a11y": "^6.6.0",
- "npm_package_node_child_process": "empty",
- "npm_package_dependencies_react_resizable_rotatable_draggable": "^0.2.0",
- "npm_package_dependencies_got": "^12.0.1",
- "npm_package_dependencies__types_d3_color": "^2.0.3",
- "npm_package_devDependencies_webpack": "^5.69.1",
- "npm_package_devDependencies__types_nodemailer": "^4.6.6",
- "npm_package_description": "Install Node.js, then, from the project directory, run",
- "NVM_DIR": "/Users/sarah/.nvm",
- "USER": "sarah",
- "npm_package_dependencies__types_d3_scale": "^3.3.2",
- "npm_package_devDependencies_dotenv": "^8.6.0",
- "npm_package_devDependencies__types_react": "^18.0.15",
- "npm_package_devDependencies__types_prosemirror_transform": "^1.1.5",
- "npm_package_devDependencies__types_prosemirror_history": "^1.0.3",
- "npm_package_dependencies_readline": "^1.3.0",
- "npm_package_dependencies__types_supercluster": "^7.1.0",
- "npm_config_globalconfig": "/Users/sarah/.nvm/versions/node/v12.16.0/etc/npmrc",
- "npm_package_dependencies_depcheck": "^0.9.2",
- "npm_package_dependencies__types_web": "0.0.53",
- "CONDA_EXE": "/Users/sarah/miniconda3/bin/conda",
- "npm_config_prefer_online": "",
- "npm_config_logs_max": "10",
- "npm_config_always_auth": "",
- "npm_package_dependencies_react_icons": "^4.3.1",
- "npm_package_dependencies_passport_google_oauth20": "^2.0.0",
- "npm_package_devDependencies_webpack_dev_server": "^3.11.3",
- "npm_package_dependencies_url_loader": "^1.1.2",
- "npm_package_dependencies_stream_browserify": "^3.0.0",
- "npm_package_dependencies_prosemirror_transform": "^1.3.4",
- "npm_package_dependencies_lodash": "^4.17.21",
- "npm_package_dependencies_i": "^0.3.7",
- "npm_package_devDependencies_tslint_loader": "^3.6.0",
- "SSH_AUTH_SOCK": "/private/tmp/com.apple.launchd.TKuATvqs9j/Listeners",
- "npm_package_dependencies_words_to_numbers": "^1.5.1",
- "npm_package_dependencies_valid_url": "^1.0.9",
- "npm_package_dependencies_styled_components": "^4.4.1",
- "npm_package_dependencies_class_transformer": "^0.2.0",
- "npm_package_devDependencies_eslint": "^8.18.0",
- "npm_package_devDependencies__types_prosemirror_inputrules": "^1.0.4",
- "npm_package_devDependencies__types_express": "^4.17.13",
- "__CF_USER_TEXT_ENCODING": "0x1F5:0x0:0x0",
- "npm_execpath": "/Users/sarah/.nvm/versions/node/v12.16.0/lib/node_modules/npm/bin/npm-cli.js",
- "npm_config_global_style": "",
- "npm_config_cache_lock_retries": "10",
- "npm_package_dependencies_wikijs": "^6.3.3",
- "npm_package_dependencies_bluebird": "^3.7.2",
- "npm_config_update_notifier": "true",
- "npm_config_cafile": "",
- "npm_package_dependencies_util": "^0.12.4",
- "npm_package_dependencies_raw_loader": "^1.0.0",
- "npm_package_dependencies_https_browserify": "^1.0.0",
- "npm_package_dependencies__fortawesome_react_fontawesome": "^0.1.19",
- "npm_package_devDependencies__types_passport_google_oauth20": "^2.0.11",
- "npm_package_dependencies_cors": "^2.8.5",
- "npm_package_dependencies_bezier_js": "^4.1.1",
- "npm_package_dependencies__fortawesome_free_brands_svg_icons": "^5.15.4",
- "npm_config_heading": "npm",
- "npm_config_audit_level": "low",
- "npm_package_dependencies_chrome": "^0.1.0",
- "npm_package_dependencies__react_three_fiber": "^6.2.3",
- "npm_package_devDependencies_eslint_plugin_prettier": "^4.2.1",
- "npm_package_devDependencies_copy_webpack_plugin": "^4.6.0",
- "npm_package_devDependencies__types_react_measure": "^2.0.8",
- "npm_package_devDependencies__types_react_dom": "^18.0.6",
- "npm_package_devDependencies__types_mobile_detect": "^1.3.4",
- "_CE_CONDA": "",
- "npm_config_searchlimit": "20",
- "npm_config_read_only": "",
- "npm_config_offline": "",
- "npm_config_fetch_retry_mintimeout": "10000",
- "npm_package_dependencies_mobx_react_devtools": "^6.1.1",
- "npm_package_dependencies_md5_file": "^5.0.0",
- "npm_package_dependencies_forever_agent": "^0.6.1",
- "npm_package_devDependencies__types_xregexp": "^4.4.0",
- "npm_package_devDependencies__types_typescript": "^2.0.0",
- "npm_package_devDependencies__types_request": "^2.48.8",
- "npm_package_devDependencies__types_prosemirror_commands": "^1.0.4",
- "npm_config_json": "",
- "npm_config_access": "",
- "npm_config_argv": "{\"remain\":[],\"cooked\":[\"start\"],\"original\":[\"start\"]}",
- "npm_package_dependencies__fortawesome_free_solid_svg_icons": "^5.15.4",
- "npm_package_devDependencies__types_socket_io": "^2.1.13",
- "PATH": "/Users/sarah/.nvm/versions/node/v12.16.0/lib/node_modules/npm/node_modules/npm-lifecycle/node-gyp-bin:/Users/sarah/Desktop/dash/Dash-Web/node_modules/.bin:/Users/sarah/.nvm/versions/node/v12.16.0/bin:/Users/sarah/miniconda3/bin:/Users/sarah/miniconda3/condabin:/Users/sarah/.elan/bin:/Library/Frameworks/Python.framework/Versions/3.9/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin",
- "npm_config_allow_same_version": "",
- "npm_package_dependencies_webrtc_adapter": "^7.7.1",
- "npm_package_dependencies_react_reveal": "^1.2.2",
- "npm_package_dependencies_prosemirror_schema_list": "^1.1.6",
- "npm_package_dependencies__material_ui_core": "^4.12.3",
- "npm_package_devDependencies__types_rimraf": "^2.0.5",
- "npm_package_devDependencies__types_connect_flash": "0.0.34",
- "npm_config_https_proxy": "",
- "npm_config_engine_strict": "",
- "npm_config_description": "true",
- "npm_package_dependencies_pug": "^2.0.4",
- "npm_package_dependencies_prosemirror_keymap": "^1.1.5",
- "npm_package_dependencies_pdfjs_dist": "^2.14.305",
- "npm_package_dependencies_mobile_detect": "^1.4.5",
- "npm_package_dependencies_image_size_stream": "^1.1.0",
- "npm_package_dependencies_golden_layout": "^1.5.9",
- "npm_package_dependencies_child_process": "^1.0.2",
- "npm_package_dependencies__types_d3_axis": "^2.1.3",
- "_": "/Users/sarah/Desktop/dash/Dash-Web/node_modules/.bin/cross-env",
- "npm_config_userconfig": "/Users/sarah/.npmrc",
- "npm_config_init_module": "/Users/sarah/.npm-init.js",
- "npm_package_dependencies__react_google_maps_api": "^2.7.0",
- "CONDA_PREFIX": "/Users/sarah/miniconda3",
- "__CFBundleIdentifier": "com.apple.Terminal",
- "npm_config_cidr": "",
- "npm_package_dependencies_puppeteer": "^3.3.0",
- "npm_package_dependencies_prosemirror_view": "^1.26.5",
- "npm_package_dependencies_mongodb": "^3.7.3",
- "npm_package_dependencies_google_auth_library": "^4.2.4",
- "npm_package_dependencies_bootstrap": "^4.6.1",
- "npm_package_devDependencies_eslint_config_airbnb": "^19.0.4",
- "PWD": "/Users/sarah/desktop/dash/dash-web",
- "npm_config_user": "501",
- "npm_config_node_version": "12.16.0",
- "npm_package_dependencies_node_sass": "^4.14.1",
- "npm_package_dependencies_howler": "^2.2.3",
- "npm_package_dependencies_expressjs": "^1.0.1",
- "npm_package_dependencies_core_js": "^3.28.0",
- "npm_package_dependencies_browndash_components": "0.0.22",
- "npm_package_devDependencies_eslint_plugin_react_hooks": "^4.6.0",
- "npm_package_devDependencies__types_lodash": "^4.14.179",
- "JAVA_HOME": "/Library/Java/JavaVirtualMachines/jdk1.8.0_341.jdk/Contents/Home",
- "npm_lifecycle_event": "start",
- "npm_package_dependencies_react_table": "^6.11.5",
- "npm_package_dependencies_react_loading": "^2.0.3",
- "npm_package_dependencies_mobx": "^5.15.7",
- "npm_package_dependencies_babel": "^6.23.0",
- "npm_package_devDependencies_jsdom": "^15.2.1",
- "npm_package_devDependencies_chai": "^4.3.6",
- "npm_config_save": "true",
- "npm_config_ignore_prepublish": "",
- "npm_config_editor": "vi",
- "npm_config_auth_type": "legacy",
- "npm_package_dependencies_npm": "^6.14.18",
- "npm_package_dependencies_node_stream_zip": "^1.15.0",
- "npm_package_dependencies_image_data_uri": "^2.0.1",
- "npm_package_scripts_start_release": "cross-env RELEASE=true NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts",
- "npm_package_name": "dash",
- "LANG": "en_US.UTF-8",
- "npm_config_tag": "latest",
- "npm_config_script_shell": "",
- "npm_package_dependencies_query_string": "^6.14.1",
- "npm_package_dependencies_mobx_utils": "^5.6.2",
- "npm_package_dependencies_file_saver": "^2.0.5",
- "npm_package_dependencies_body_parser": "^1.19.2",
- "npm_package_dependencies__types_reveal": "^3.3.33",
- "npm_package_devDependencies_eslint_plugin_import": "^2.26.0",
- "npm_package_devDependencies__types_prosemirror_view": "^1.23.1",
- "npm_config_progress": "true",
- "npm_config_global": "",
- "npm_config_before": "",
- "npm_package_dependencies_xoauth2": "^1.2.0",
- "npm_package_dependencies_standard_http_error": "^2.0.1",
- "npm_package_dependencies_http_browserify": "^1.7.0",
- "npm_package_dependencies__types_d3_selection": "^2.0.1",
- "npm_package_dependencies__hig_flyout": "^1.3.1",
- "npm_package_devDependencies_fork_ts_checker_webpack_plugin": "^1.6.0",
- "npm_package_scripts_build": "cross-env NODE_OPTIONS=--max_old_space_size=8192 webpack --env production",
- "npm_package_scripts_start": "cross-env NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev --debug --transpile-only -- src/server/index.ts",
- "npm_config_searchstaleness": "900",
- "npm_config_optional": "true",
- "npm_config_ham_it_up": "",
- "npm_package_dependencies_sharp": "^0.23.4",
- "npm_package_dependencies_rc_switch": "^1.9.2",
- "npm_package_dependencies_googlephotos": "^0.2.5",
- "npm_package_dependencies_exifr": "^7.1.3",
- "npm_package_dependencies__types_google_maps": "^3.2.3",
- "npm_package_dependencies__types_bezier_js": "^4.1.0",
- "npm_package_dependencies__ffmpeg_core": "0.10.0",
- "npm_package_devDependencies_ts_loader": "^5.3.3",
- "npm_package_devDependencies__types_bcrypt_nodejs": "0.0.30",
- "XPC_FLAGS": "0x0",
- "npm_config_save_prod": "",
- "npm_config_force": "",
- "npm_config_bin_links": "true",
- "npm_package_devDependencies__types_youtube": "0.0.39",
- "npm_config_searchopts": "",
- "npm_package_dependencies_react_beautiful_dnd": "^13.1.0",
- "npm_package_dependencies_jszip": "^3.7.1",
- "npm_package_devDependencies__types_react_icons": "^3.0.0",
- "npm_config_node_gyp": "/Users/sarah/.nvm/versions/node/v12.16.0/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js",
- "npm_config_depth": "Infinity",
- "npm_package_dependencies_google_maps_react": "^2.0.6",
- "npm_package_dependencies_express_session": "^1.17.2",
- "npm_package_devDependencies_eslint_plugin_node": "^11.1.0",
- "npm_package_devDependencies_eslint_config_prettier": "^8.5.0",
- "npm_package_main": "index.js",
- "npm_config_sso_poll_frequency": "500",
- "npm_config_rebuild_bundle": "true",
- "npm_package_devDependencies__types_prosemirror_menu": "^1.0.6",
- "npm_package_devDependencies__types_prosemirror_keymap": "^1.0.4",
- "npm_package_devDependencies__types_pdfjs_dist": "^2.10.378",
- "npm_package_devDependencies__types_exif": "^0.6.3",
- "npm_package_version": "1.0.0",
- "_CE_M": "",
- "XPC_SERVICE_NAME": "0",
- "npm_config_unicode": "true",
- "npm_package_dependencies_typescript_language_server": "^0.4.0",
- "npm_package_dependencies_prosemirror_model": "^1.18.1",
- "npm_package_dependencies__ffmpeg_ffmpeg": "0.10.0",
- "SHLVL": "2",
- "HOME": "/Users/sarah",
- "npm_config_fetch_retry_maxtimeout": "60000",
- "npm_package_dependencies_request_promise": "^4.2.6",
- "npm_package_dependencies_react_markdown": "^8.0.3",
- "npm_package_dependencies__hig_theme_context": "^2.1.3",
- "npm_package_devDependencies__types_react_autosuggest": "^9.3.14",
- "npm_package_devDependencies__types_mongoose": "^5.11.97",
- "npm_package_devDependencies__types_animejs": "^2.0.2",
- "npm_package_scripts_test": "mocha -r ts-node/register test/**/*.ts",
- "npm_config_tag_version_prefix": "v",
- "npm_config_strict_ssl": "true",
- "npm_config_sso_type": "oauth",
- "npm_config_scripts_prepend_node_path": "warn-only",
- "npm_config_save_prefix": "^",
- "npm_config_loglevel": "notice",
- "npm_config_ca": "",
- "npm_package_dependencies_three": "^0.127.0",
- "npm_package_dependencies_mobx_react": "^5.4.4",
- "npm_package_dependencies_cookie_parser": "^1.4.6",
- "npm_package_dependencies_adm_zip": "^0.4.16",
- "npm_package_devDependencies_eslint_config_node": "^4.1.0",
- "npm_config_save_exact": "",
- "npm_config_group": "20",
- "npm_config_fetch_retry_factor": "10",
- "npm_config_dev": "",
- "npm_package_devDependencies_webpack_hot_middleware": "^2.25.1",
- "npm_package_devDependencies_cross_env": "^5.2.1",
- "npm_config_version": "",
- "npm_config_prefer_offline": "",
- "npm_config_cache_lock_stale": "60000",
- "npm_package_devDependencies__types_prosemirror_state": "^1.2.8",
- "npm_package_devDependencies__types_body_parser": "^1.19.2",
- "npm_config_otp": "",
- "npm_config_cache_min": "10",
- "npm_package_dependencies_react_color": "^2.19.3",
- "npm_package_devDependencies_ts_node": "^10.9.1",
- "npm_package_devDependencies__types_react_grid_layout": "^1.3.2",
- "npm_config_searchexclude": "",
- "npm_config_cache": "/Users/sarah/.npm",
- "npm_package_dependencies_tough_cookie": "^4.0.0",
- "npm_package_dependencies_googleapis": "^40.0.0",
- "npm_package_devDependencies__types_valid_url": "^1.0.3",
- "npm_package_devDependencies__types_passport": "^1.0.9",
- "npm_package_devDependencies__types_adm_zip": "^0.4.34",
- "CONDA_PYTHON_EXE": "/Users/sarah/miniconda3/bin/python",
- "LOGNAME": "sarah",
- "npm_lifecycle_script": "cross-env NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev --debug --transpile-only -- src/server/index.ts",
- "npm_config_color": "true",
- "npm_package_dependencies_solr_node": "^1.2.1",
- "npm_package_dependencies_react_transition_group": "^4.4.2",
- "npm_package_dependencies_iink_js": "^1.5.4",
- "npm_package_dependencies_html_webpack_plugin": "^5.5.0",
- "npm_config_proxy": "",
- "npm_config_package_lock": "true",
- "npm_package_dependencies_prosemirror_state": "^1.4.1",
- "npm_package_dependencies_nodemon": "^1.19.4",
- "npm_package_dependencies_function_plot": "^1.22.8",
- "npm_package_dependencies_equation_editor_react": "github:bobzel/equation-editor-react#useLocally",
- "npm_package_devDependencies__types_socket_io_parser": "^3.0.0",
- "CLASSPATH": "/Users/sarah/Downloads/cs15/*:.",
- "npm_config_package_lock_only": "",
- "npm_config_fund": "true",
- "npm_package_dependencies_react": "^18.2.0",
- "npm_package_dependencies_bingmaps_react": "^1.2.10",
- "npm_package_devDependencies_scss_loader": "0.0.1",
- "npm_package_devDependencies__types_cookie_session": "^2.0.44",
- "npm_config_save_optional": "",
- "npm_package_dependencies_textarea_caret": "^3.1.0",
- "npm_package_dependencies_react_measure": "^2.5.2",
- "npm_package_dependencies_exif": "^0.6.0",
- "NVM_BIN": "/Users/sarah/.nvm/versions/node/v12.16.0/bin",
- "CONDA_DEFAULT_ENV": "base",
- "npm_config_ignore_scripts": "",
- "npm_config_user_agent": "npm/6.14.7 node/v12.16.0 darwin x64",
- "npm_package_dependencies_react_resizable": "^1.11.1",
- "npm_package_dependencies_prosemirror_commands": "^1.2.1",
- "npm_package_dependencies_memorystream": "^0.3.1",
- "npm_package_dependencies_formidable": "1.2.1",
- "npm_package_devDependencies__types_uuid": "^3.4.10",
- "npm_config_cache_lock_wait": "10000",
- "npm_package_dependencies_socket_io_client": "^2.5.0",
- "npm_package_dependencies_fluent_ffmpeg": "^2.1.2",
- "npm_package_dependencies__types_cors": "^2.8.12",
- "npm_package_devDependencies__types_node": "^10.17.60",
- "npm_package_devDependencies__types_file_saver": "^2.0.5",
- "npm_config_production": "",
- "npm_package_dependencies_jsonschema": "^1.4.0",
- "npm_package_dependencies_ffmpeg": "0.0.4",
- "npm_package_dependencies_cookie_session": "^2.0.0",
- "npm_package_dependencies_color": "^3.2.1",
- "npm_package_devDependencies__types_webpack": "^4.41.32",
- "npm_package_devDependencies__types_request_promise": "^4.1.48",
- "npm_package_devDependencies__types_prosemirror_schema_list": "^1.0.3",
- "npm_config_send_metrics": "",
- "npm_config_save_bundle": "",
- "npm_package_dependencies_web_request": "^1.0.7",
- "npm_package_dependencies_react_datepicker": "^3.8.0",
- "npm_package_dependencies_express": "^4.17.3",
- "npm_package_dependencies_D": "^1.0.0",
- "npm_package_dependencies__types_formidable": "1.0.31",
- "npm_package_devDependencies__types_rc_switch": "^1.9.2",
- "npm_package_devDependencies__types_prosemirror_dev_tools": "^2.1.0",
- "npm_package_devDependencies__types_jquery": "^3.5.14",
- "npm_config_umask": "0022",
- "npm_config_node_options": "",
- "npm_config_init_version": "1.0.0",
- "npm_package_dependencies_https": "^1.0.0",
- "npm_package_dependencies_array_batcher": "^1.2.3",
- "npm_package_dependencies__fortawesome_free_regular_svg_icons": "^5.15.4",
- "npm_package_devDependencies__types_shelljs": "^0.8.11",
- "npm_package_devDependencies__types_libxmljs": "^0.18.7",
- "npm_package_devDependencies__types_express_validator": "^3.0.0",
- "npm_package_devDependencies__types_bluebird": "^3.5.36",
- "npm_config_init_author_name": "",
- "npm_config_git": "git",
- "npm_config_scope": "",
- "npm_package_dependencies_react_select": "^3.2.0",
- "npm_package_dependencies_pdf_parse": "^1.1.1",
- "npm_package_dependencies_colors": "^1.4.0",
- "npm_package_dependencies_archiver": "^3.1.1",
- "npm_package_devDependencies_css_loader": "^2.1.1",
- "npm_package_devDependencies__types_socket_io_client": "^1.4.36",
- "npm_config_unsafe_perm": "true",
- "npm_config_tmp": "/var/folders/yk/p_39q8jn673c5p8_66mcxm7r0000gn/T",
- "npm_config_onload_script": "",
- "npm_package_dependencies_serializr": "^1.5.4",
- "npm_package_dependencies_fit_curve": "^0.1.7",
- "npm_package_dependencies__webscopeio_react_textarea_autocomplete": "^4.9.1",
- "npm_package_dependencies__types_three": "^0.126.2",
- "npm_package_devDependencies_ts_node_dev": "^2.0.0",
- "npm_node_execpath": "/Users/sarah/.nvm/versions/node/v12.16.0/bin/node",
- "npm_config_prefix": "/Users/sarah/.nvm/versions/node/v12.16.0",
- "npm_config_link": "",
- "npm_config_format_package_lock": "true",
- "npm_package_dependencies_passport": "^0.4.0",
- "npm_package_devDependencies_eslint_plugin_react": "^7.30.1",
- "npm_package_devDependencies__types_react_table": "^6.8.9",
- "npm_package_devDependencies__types_react_reconciler": "^0.26.4",
- "NODE_OPTIONS": "--max_old_space_size=4096",
- "TS_NODE_DEV": "true",
- "VIPSHOME": "/usr/local/Cellar/vips/8.8.1",
- "TYPESCRIPT_PATH": "/Users/sarah/Desktop/dash/Dash-Web/node_modules/typescript/lib/typescript.js",
- "TSCONFIG": "/Users/sarah/Desktop/dash/Dash-Web/tsconfig.json",
- "COMPILER_OPTIONS": "{}",
- "TSLINT": "true",
- "CONTEXT": "/Users/sarah/Desktop/dash/Dash-Web",
- "TSLINTAUTOFIX": "false",
- "ESLINT": "false",
- "ESLINT_OPTIONS": "{}",
- "WATCH": "",
- "WORK_DIVISION": "1",
- "MEMORY_LIMIT": "2048",
- "CHECK_SYNTACTIC_ERRORS": "false",
- "USE_INCREMENTAL_API": "true",
- "VUE": "false"
- },
- "userLimits": {
- "core_file_size_blocks": {
- "soft": 0,
- "hard": "unlimited"
- },
- "data_seg_size_kbytes": {
- "soft": "unlimited",
- "hard": "unlimited"
- },
- "file_size_blocks": {
- "soft": "unlimited",
- "hard": "unlimited"
- },
- "max_locked_memory_bytes": {
- "soft": "unlimited",
- "hard": "unlimited"
- },
- "max_memory_size_kbytes": {
- "soft": "unlimited",
- "hard": "unlimited"
- },
- "open_files": {
- "soft": 1048575,
- "hard": "unlimited"
- },
- "stack_size_bytes": {
- "soft": 8388608,
- "hard": 67104768
- },
- "cpu_time_seconds": {
- "soft": "unlimited",
- "hard": "unlimited"
- },
- "max_user_processes": {
- "soft": 2784,
- "hard": 4176
- },
- "virtual_memory_kbytes": {
- "soft": "unlimited",
- "hard": "unlimited"
- }
- },
- "sharedObjects": [
- "/Users/sarah/.nvm/versions/node/v12.16.0/bin/node",
- "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation",
- "/usr/lib/libSystem.B.dylib",
- "/usr/lib/libc++.1.dylib",
- "/usr/lib/libobjc.A.dylib",
- "/usr/lib/liboah.dylib",
- "/usr/lib/libfakelink.dylib",
- "/usr/lib/libicucore.A.dylib",
- "/System/Library/PrivateFrameworks/SoftLinking.framework/Versions/A/SoftLinking",
- "/usr/lib/libc++abi.dylib",
- "/usr/lib/system/libcache.dylib",
- "/usr/lib/system/libcommonCrypto.dylib",
- "/usr/lib/system/libcompiler_rt.dylib",
- "/usr/lib/system/libcopyfile.dylib",
- "/usr/lib/system/libcorecrypto.dylib",
- "/usr/lib/system/libdispatch.dylib",
- "/usr/lib/system/libdyld.dylib",
- "/usr/lib/system/libkeymgr.dylib",
- "/usr/lib/system/liblaunch.dylib",
- "/usr/lib/system/libmacho.dylib",
- "/usr/lib/system/libquarantine.dylib",
- "/usr/lib/system/libremovefile.dylib",
- "/usr/lib/system/libsystem_asl.dylib",
- "/usr/lib/system/libsystem_blocks.dylib",
- "/usr/lib/system/libsystem_c.dylib",
- "/usr/lib/system/libsystem_collections.dylib",
- "/usr/lib/system/libsystem_configuration.dylib",
- "/usr/lib/system/libsystem_containermanager.dylib",
- "/usr/lib/system/libsystem_coreservices.dylib",
- "/usr/lib/system/libsystem_darwin.dylib",
- "/usr/lib/system/libsystem_dnssd.dylib",
- "/usr/lib/system/libsystem_featureflags.dylib",
- "/usr/lib/system/libsystem_info.dylib",
- "/usr/lib/system/libsystem_m.dylib",
- "/usr/lib/system/libsystem_malloc.dylib",
- "/usr/lib/system/libsystem_networkextension.dylib",
- "/usr/lib/system/libsystem_notify.dylib",
- "/usr/lib/system/libsystem_product_info_filter.dylib",
- "/usr/lib/system/libsystem_sandbox.dylib",
- "/usr/lib/system/libsystem_secinit.dylib",
- "/usr/lib/system/libsystem_kernel.dylib",
- "/usr/lib/system/libsystem_platform.dylib",
- "/usr/lib/system/libsystem_pthread.dylib",
- "/usr/lib/system/libsystem_symptoms.dylib",
- "/usr/lib/system/libsystem_trace.dylib",
- "/usr/lib/system/libunwind.dylib",
- "/usr/lib/system/libxpc.dylib",
- "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices",
- "/System/Library/Frameworks/CoreGraphics.framework/Versions/A/CoreGraphics",
- "/System/Library/Frameworks/CoreText.framework/Versions/A/CoreText",
- "/System/Library/Frameworks/ImageIO.framework/Versions/A/ImageIO",
- "/System/Library/Frameworks/ColorSync.framework/Versions/A/ColorSync",
- "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATS.framework/Versions/A/ATS",
- "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ColorSyncLegacy.framework/Versions/A/ColorSyncLegacy",
- "/System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices",
- "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/HIServices",
- "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/LangAnalysis.framework/Versions/A/LangAnalysis",
- "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/PrintCore.framework/Versions/A/PrintCore",
- "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/QD.framework/Versions/A/QD",
- "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/SpeechSynthesis.framework/Versions/A/SpeechSynthesis",
- "/System/Library/PrivateFrameworks/SkyLight.framework/Versions/A/SkyLight",
- "/System/Library/PrivateFrameworks/FontServices.framework/libFontParser.dylib",
- "/System/Library/Frameworks/Accelerate.framework/Versions/A/Accelerate",
- "/System/Library/Frameworks/IOSurface.framework/Versions/A/IOSurface",
- "/usr/lib/libxml2.2.dylib",
- "/System/Library/Frameworks/CFNetwork.framework/Versions/A/CFNetwork",
- "/usr/lib/libz.1.dylib",
- "/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation",
- "/System/Library/PrivateFrameworks/RunningBoardServices.framework/Versions/A/RunningBoardServices",
- "/usr/lib/libMobileGestalt.dylib",
- "/System/Library/PrivateFrameworks/WatchdogClient.framework/Versions/A/WatchdogClient",
- "/usr/lib/libcompression.dylib",
- "/usr/lib/libDiagnosticMessagesClient.dylib",
- "/System/Library/Frameworks/SystemConfiguration.framework/Versions/A/SystemConfiguration",
- "/System/Library/Frameworks/CoreDisplay.framework/Versions/A/CoreDisplay",
- "/System/Library/Frameworks/CoreMedia.framework/Versions/A/CoreMedia",
- "/System/Library/PrivateFrameworks/IOAccelerator.framework/Versions/A/IOAccelerator",
- "/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit",
- "/System/Library/Frameworks/Metal.framework/Versions/A/Metal",
- "/System/Library/Frameworks/CoreVideo.framework/Versions/A/CoreVideo",
- "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/MetalPerformanceShaders",
- "/System/Library/PrivateFrameworks/MultitouchSupport.framework/Versions/A/MultitouchSupport",
- "/System/Library/Frameworks/Security.framework/Versions/A/Security",
- "/System/Library/Frameworks/QuartzCore.framework/Versions/A/QuartzCore",
- "/usr/lib/libbsm.0.dylib",
- "/System/Library/PrivateFrameworks/CoreAnalytics.framework/Versions/A/CoreAnalytics",
- "/System/Library/Frameworks/VideoToolbox.framework/Versions/A/VideoToolbox",
- "/System/Library/PrivateFrameworks/BaseBoard.framework/Versions/A/BaseBoard",
- "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/FSEvents.framework/Versions/A/FSEvents",
- "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/CarbonCore.framework/Versions/A/CarbonCore",
- "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/Metadata.framework/Versions/A/Metadata",
- "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/OSServices.framework/Versions/A/OSServices",
- "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/SearchKit.framework/Versions/A/SearchKit",
- "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/AE.framework/Versions/A/AE",
- "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/LaunchServices",
- "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/DictionaryServices.framework/Versions/A/DictionaryServices",
- "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/SharedFileList.framework/Versions/A/SharedFileList",
- "/usr/lib/libapple_nghttp2.dylib",
- "/usr/lib/libnetwork.dylib",
- "/usr/lib/libsqlite3.dylib",
- "/usr/lib/libenergytrace.dylib",
- "/usr/lib/system/libkxld.dylib",
- "/System/Library/PrivateFrameworks/AppleFSCompression.framework/Versions/A/AppleFSCompression",
- "/usr/lib/libcoretls.dylib",
- "/usr/lib/libcoretls_cfhelpers.dylib",
- "/usr/lib/libpam.2.dylib",
- "/usr/lib/libxar.1.dylib",
- "/System/Library/PrivateFrameworks/CoreAutoLayout.framework/Versions/A/CoreAutoLayout",
- "/System/Library/Frameworks/DiskArbitration.framework/Versions/A/DiskArbitration",
- "/usr/lib/libarchive.2.dylib",
- "/usr/lib/liblangid.dylib",
- "/usr/lib/libCRFSuite.dylib",
- "/usr/lib/libpcap.A.dylib",
- "/usr/lib/libdns_services.dylib",
- "/usr/lib/liblzma.5.dylib",
- "/usr/lib/libbz2.1.0.dylib",
- "/usr/lib/libiconv.2.dylib",
- "/usr/lib/libcharset.1.dylib",
- "/System/Library/PrivateFrameworks/AppleSystemInfo.framework/Versions/A/AppleSystemInfo",
- "/System/Library/PrivateFrameworks/IOMobileFramebuffer.framework/Versions/A/IOMobileFramebuffer",
- "/usr/lib/libCheckFix.dylib",
- "/System/Library/PrivateFrameworks/TCC.framework/Versions/A/TCC",
- "/System/Library/PrivateFrameworks/CoreNLP.framework/Versions/A/CoreNLP",
- "/System/Library/PrivateFrameworks/MetadataUtilities.framework/Versions/A/MetadataUtilities",
- "/usr/lib/libmecabra.dylib",
- "/System/Library/Frameworks/MLCompute.framework/Versions/A/MLCompute",
- "/usr/lib/libmecab.dylib",
- "/usr/lib/libgermantok.dylib",
- "/usr/lib/libThaiTokenizer.dylib",
- "/usr/lib/libChineseTokenizer.dylib",
- "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vImage.framework/Versions/A/vImage",
- "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/vecLib",
- "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libvMisc.dylib",
- "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libvDSP.dylib",
- "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib",
- "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libLAPACK.dylib",
- "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libLinearAlgebra.dylib",
- "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libSparseBLAS.dylib",
- "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libQuadrature.dylib",
- "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBNNS.dylib",
- "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libSparse.dylib",
- "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSCore.framework/Versions/A/MPSCore",
- "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSImage.framework/Versions/A/MPSImage",
- "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSNeuralNetwork.framework/Versions/A/MPSNeuralNetwork",
- "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSMatrix.framework/Versions/A/MPSMatrix",
- "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSRayIntersector.framework/Versions/A/MPSRayIntersector",
- "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSNDArray.framework/Versions/A/MPSNDArray",
- "/System/Library/PrivateFrameworks/MetalTools.framework/Versions/A/MetalTools",
- "/System/Library/PrivateFrameworks/AggregateDictionary.framework/Versions/A/AggregateDictionary",
- "/System/Library/PrivateFrameworks/AppleSauce.framework/Versions/A/AppleSauce",
- "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCoreFSCache.dylib",
- "/System/Library/PrivateFrameworks/LanguageModeling.framework/Versions/A/LanguageModeling",
- "/System/Library/PrivateFrameworks/CoreEmoji.framework/Versions/A/CoreEmoji",
- "/System/Library/PrivateFrameworks/LinguisticData.framework/Versions/A/LinguisticData",
- "/System/Library/PrivateFrameworks/Lexicon.framework/Versions/A/Lexicon",
- "/usr/lib/libcmph.dylib",
- "/System/Library/Frameworks/OpenDirectory.framework/Versions/A/Frameworks/CFOpenDirectory.framework/Versions/A/CFOpenDirectory",
- "/System/Library/Frameworks/OpenDirectory.framework/Versions/A/OpenDirectory",
- "/System/Library/PrivateFrameworks/APFS.framework/Versions/A/APFS",
- "/System/Library/Frameworks/SecurityFoundation.framework/Versions/A/SecurityFoundation",
- "/usr/lib/libutil.dylib",
- "/usr/lib/libapp_launch_measurement.dylib",
- "/System/Library/PrivateFrameworks/CoreServicesStore.framework/Versions/A/CoreServicesStore",
- "/System/Library/Frameworks/ServiceManagement.framework/Versions/A/ServiceManagement",
- "/usr/lib/libxslt.1.dylib",
- "/System/Library/PrivateFrameworks/BackgroundTaskManagement.framework/Versions/A/BackgroundTaskManagement",
- "/System/Library/PrivateFrameworks/PersistentConnection.framework/Versions/A/PersistentConnection",
- "/System/Library/PrivateFrameworks/ProtocolBuffer.framework/Versions/A/ProtocolBuffer",
- "/System/Library/PrivateFrameworks/CommonUtilities.framework/Versions/A/CommonUtilities",
- "/System/Library/PrivateFrameworks/Bom.framework/Versions/A/Bom",
- "/usr/lib/libate.dylib",
- "/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libRadiance.dylib",
- "/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libJPEG.dylib",
- "/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libPng.dylib",
- "/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libTIFF.dylib",
- "/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libGIF.dylib",
- "/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libJP2.dylib",
- "/usr/lib/libexpat.1.dylib",
- "/System/Library/PrivateFrameworks/AppleJPEG.framework/Versions/A/AppleJPEG",
- "/System/Library/PrivateFrameworks/GPUWrangler.framework/Versions/A/GPUWrangler",
- "/System/Library/PrivateFrameworks/IOPresentment.framework/Versions/A/IOPresentment",
- "/System/Library/PrivateFrameworks/DSExternalDisplay.framework/Versions/A/DSExternalDisplay",
- "/System/Library/PrivateFrameworks/CMCaptureCore.framework/Versions/A/CMCaptureCore",
- "/usr/lib/libspindump.dylib",
- "/System/Library/Frameworks/CoreAudio.framework/Versions/A/CoreAudio",
- "/System/Library/PrivateFrameworks/AppServerSupport.framework/Versions/A/AppServerSupport",
- "/System/Library/PrivateFrameworks/perfdata.framework/Versions/A/perfdata",
- "/System/Library/PrivateFrameworks/AssertionServices.framework/Versions/A/AssertionServices",
- "/System/Library/PrivateFrameworks/AudioToolboxCore.framework/Versions/A/AudioToolboxCore",
- "/System/Library/PrivateFrameworks/caulk.framework/Versions/A/caulk",
- "/System/Library/PrivateFrameworks/SystemPolicy.framework/Versions/A/SystemPolicy",
- "/usr/lib/libIOReport.dylib",
- "/usr/lib/libSMC.dylib",
- "/usr/lib/libAudioToolboxUtility.dylib",
- "/usr/lib/libmis.dylib",
- "/System/Library/Frameworks/OpenGL.framework/Versions/A/OpenGL",
- "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGLU.dylib",
- "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGFXShared.dylib",
- "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib",
- "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGLImage.dylib",
- "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCVMSPluginSupport.dylib",
- "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCoreVMClient.dylib",
- "/System/Library/Frameworks/CoreImage.framework/Versions/A/CoreImage",
- "/System/Library/Frameworks/OpenCL.framework/Versions/A/OpenCL",
- "/System/Library/PrivateFrameworks/GraphVisualizer.framework/Versions/A/GraphVisualizer",
- "/System/Library/PrivateFrameworks/FaceCore.framework/Versions/A/FaceCore",
- "/System/Library/PrivateFrameworks/OTSVG.framework/Versions/A/OTSVG",
- "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATS.framework/Versions/A/Resources/libFontRegistry.dylib",
- "/System/Library/PrivateFrameworks/FontServices.framework/libhvf.dylib",
- "/System/Library/PrivateFrameworks/AppleVA.framework/Versions/A/AppleVA",
- "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATSUI.framework/Versions/A/ATSUI",
- "/usr/lib/libcups.2.dylib",
- "/System/Library/PrivateFrameworks/NetAuth.framework/Versions/A/NetAuth",
- "/System/Library/Frameworks/Kerberos.framework/Versions/A/Kerberos",
- "/System/Library/Frameworks/GSS.framework/Versions/A/GSS",
- "/usr/lib/libresolv.9.dylib",
- "/System/Library/PrivateFrameworks/Heimdal.framework/Versions/A/Heimdal",
- "/System/Library/Frameworks/Kerberos.framework/Versions/A/Libraries/libHeimdalProxy.dylib",
- "/System/Library/Frameworks/Network.framework/Versions/A/Network",
- "/usr/lib/libheimdal-asn1.dylib",
- "/System/Library/PrivateFrameworks/CommonAuth.framework/Versions/A/CommonAuth",
- "/System/Library/PrivateFrameworks/login.framework/Versions/A/Frameworks/loginsupport.framework/Versions/A/loginsupport",
- "/System/Library/Frameworks/AudioToolbox.framework/Versions/A/AudioToolbox",
- "/System/Library/PrivateFrameworks/AudioSession.framework/Versions/A/AudioSession",
- "/usr/lib/libAudioStatistics.dylib",
- "/System/Library/PrivateFrameworks/MediaExperience.framework/Versions/A/MediaExperience",
- "/System/Library/PrivateFrameworks/AudioSession.framework/libSessionUtility.dylib",
- "/usr/lib/libperfcheck.dylib",
- "/System/Library/PrivateFrameworks/AudioResourceArbitration.framework/Versions/A/AudioResourceArbitration",
- "/System/Library/Frameworks/CoreData.framework/Versions/A/CoreData",
- "/Users/sarah/Desktop/dash/Dash-Web/node_modules/fsevents/build/Release/fse.node"
- ]
-} \ No newline at end of file
diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 4b3960902..6bde7989b 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -14,7 +14,7 @@ type GPTCallOpts = {
};
const callTypeMap: { [type: string]: GPTCallOpts } = {
- summary: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: 'Summarize this text briefly: ' },
+ 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: '' },
};
@@ -39,7 +39,6 @@ const gptAPICall = async (inputText: string, callType: GPTCallType) => {
temperature: opts.temp,
prompt: `${opts.prompt}${inputText}`,
});
- console.log(response.data.choices[0]);
return response.data.choices[0].text;
} catch (err) {
console.log(err);
@@ -47,7 +46,7 @@ const gptAPICall = async (inputText: string, callType: GPTCallType) => {
}
};
-const gptImageCall = async (prompt: string) => {
+const gptImageCall = async (prompt: string, n?: number) => {
try {
const configuration = new Configuration({
apiKey: process.env.OPENAI_KEY,
@@ -55,33 +54,15 @@ const gptImageCall = async (prompt: string) => {
const openai = new OpenAIApi(configuration);
const response = await openai.createImage({
prompt: prompt,
- n: 1,
+ n: n ?? 1,
size: '1024x1024',
});
- return response.data.data[0].url;
+ return response.data.data.map(data => data.url);
+ // return response.data.data[0].url;
} catch (err) {
console.error(err);
return;
}
};
-// const gptEditCall = async (selectedText: string, fullText: string) => {
-// try {
-// const configuration = new Configuration({
-// apiKey: process.env.OPENAI_KEY,
-// });
-// const openai = new OpenAIApi(configuration);
-// const response = await openai.createCompletion({
-// model: 'text-davinci-003',
-// max_tokens: 256,
-// temperature: 0.1,
-// prompt: `Replace the phrase ${selectedText} inside of ${fullText}.`,
-// });
-// return response.data.choices[0].text.trim();
-// } catch (err) {
-// console.log(err);
-// return 'Error connecting with API.';
-// }
-// };
-
export { gptAPICall, gptImageCall, GPTCallType };
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 7b11e59eb..7c048334e 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -630,12 +630,14 @@ export class CurrentUserUtils {
}
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(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
- { title: "Grid", icon: "border-all", toolTip: "Show Grid", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"grid", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
- { title: "View All", icon: "object-group", toolTip: "Fit all Docs to View", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"viewAll", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
- { title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"clusters", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
- { title: "Cards", icon: "brain", toolTip: "Flashcards", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"flashcards", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
- { title: "Arrange",icon:"arrow-down-short-wide",toolTip:"Toggle Auto Arrange", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"arrange", funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+ { title: "Snap", icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"snaplines", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+ { title: "Grid", icon: "border-all", toolTip: "Show Grid", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"grid", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+ { title: "View All", icon: "object-group", toolTip: "Fit all Docs to View",btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"viewAll", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+ // want the same style as toggle button, but don't want it to act as an actual toggle, so set disableToggle to true,
+ { title: "Fit All", icon: "arrows-left-right", toolTip: "Fit all Docs to View (persistent)", btnType: ButtonType.ClickButton, ignoreClick: false, expertMode: false, toolType:"viewAllPersist", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+ { title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"clusters", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+ { title: "Cards", icon: "brain", toolTip: "Flashcards", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"flashcards", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+ { title: "Arrange", icon:"arrow-down-short-wide",toolTip:"Toggle Auto Arrange", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"arrange", funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
]
}
static textTools():Button[] {
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 306092ee4..40bf57555 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -196,7 +196,7 @@ export namespace DragManager {
}
// drag a document and drop it (or make an embed/copy on drop)
- export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions, dropEvent?: () => any) {
+ export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions, onDropCompleted?: (e?: DragCompleteEvent) => any) {
const addAudioTag = (dropDoc: any) => {
dropDoc && !dropDoc.author_date && (dropDoc.author_date = new DateField());
dropDoc instanceof Doc && DocUtils.MakeLinkToActiveAudio(() => dropDoc);
@@ -204,7 +204,7 @@ export namespace DragManager {
};
const finishDrag = async (e: DragCompleteEvent) => {
const docDragData = e.docDragData;
- dropEvent?.(); // glr: optional additional function to be called - in this case with presentation trails
+ onDropCompleted?.(e); // glr: optional additional function to be called - in this case with presentation trails
if (docDragData && !docDragData.droppedDocuments.length) {
docDragData.dropAction = dragData.userDropAction || dragData.dropAction;
docDragData.droppedDocuments = (
diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx
index afc880a7b..a0fc617ab 100644
--- a/src/client/util/RTFMarkup.tsx
+++ b/src/client/util/RTFMarkup.tsx
@@ -31,7 +31,7 @@ export class RTFMarkup extends React.Component<{}> {
*/
@computed get cheatSheet() {
return (
- <div style={{ background: SettingsManager.Instance.userBackgroundColor, color: SettingsManager.Instance.userColor, textAlign: 'initial', height: '100%' }}>
+ <div style={{ background: SettingsManager.Instance?.userBackgroundColor, color: SettingsManager.Instance?.userColor, textAlign: 'initial', height: '100%' }}>
<p>
<b style={{ fontSize: 'larger' }}>{`wiki:phrase`}</b>
{` display wikipedia page for entered text (terminate with carriage return)`}
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index a2e5e54fe..8133e9eff 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -259,6 +259,15 @@ export class SettingsManager extends React.Component<{}> {
size={Size.XSMALL}
color={this.userColor}
/>
+ <Toggle
+ formLabel={'Show Link Lines'}
+ formLabelPlacement={'right'}
+ toggleType={ToggleType.SWITCH}
+ onClick={e => (Doc.UserDoc().showLinkLines = !Doc.UserDoc().showLinkLines)}
+ toggleStatus={BoolCast(Doc.UserDoc().showLinkLines)}
+ size={Size.XSMALL}
+ color={this.userColor}
+ />
</div>
);
}
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
index 3e4827c83..231a2d5fb 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -47,12 +47,12 @@ export class DashboardView extends React.Component {
@action abortCreateNewDashboard = () => {
this.newDashboardName = undefined;
};
- @action setNewDashboardName(name: string) {
+ @action setNewDashboardName = (name: string) => {
this.newDashboardName = name;
- }
- @action setNewDashboardColor(color: string) {
+ };
+ @action setNewDashboardColor = (color: string) => {
this.newDashboardColor = color;
- }
+ };
@action
selectDashboardGroup = (group: DashboardGroup) => {
@@ -176,7 +176,7 @@ export class DashboardView extends React.Component {
</div>
<div className="all-dashboards">
{this.getDashboards(this.selectedDashboardGroup).map(dashboard => {
- const href = ImageCast(dashboard.thumb)?.url.href;
+ const href = ImageCast(dashboard.thumb)?.url?.href;
const shared = Object.keys(dashboard[DocAcl])
.filter(key => key !== `acl-${Doc.CurrentUserEmailNormalized}` && !['acl-Me', 'acl-Guest'].includes(key))
.some(key => dashboard[DocAcl][key] !== AclPrivate);
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index e376c4fdf..a785ffd42 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -55,12 +55,15 @@ import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from './n
import { DashFieldViewMenu } from './nodes/formattedText/DashFieldView';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
import { RichTextMenu } from './nodes/formattedText/RichTextMenu';
+import GenerativeFill from './nodes/generativeFill/GenerativeFill';
+import { ImageBox } from './nodes/ImageBox';
import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
import { LinkDocPreview } from './nodes/LinkDocPreview';
import { RadialMenu } from './nodes/RadialMenu';
import { TaskCompletionBox } from './nodes/TaskCompletedBox';
import { OverlayView } from './OverlayView';
import { AnchorMenu } from './pdf/AnchorMenu';
+import { GPTPopup } from './pdf/GPTPopup/GPTPopup';
import { PreviewCursor } from './PreviewCursor';
import { PropertiesView } from './PropertiesView';
import { DashboardStyleProvider, DefaultStyleProvider } from './StyleProvider';
@@ -72,6 +75,7 @@ export class MainView extends React.Component {
public static Instance: MainView;
public static Live: boolean = false;
private _docBtnRef = React.createRef<HTMLDivElement>();
+
@observable public LastButton: Opt<Doc>;
@observable private _windowWidth: number = 0;
@observable private _windowHeight: number = 0;
@@ -339,6 +343,7 @@ export class MainView extends React.Component {
fa.faMousePointer,
fa.faMusic,
fa.faObjectGroup,
+ fa.faArrowsLeftRight,
fa.faPause,
fa.faPen,
fa.faPenNib,
@@ -1006,13 +1011,15 @@ export class MainView extends React.Component {
<AnchorMenu />
<DashFieldViewMenu />
<MarqueeOptionsMenu />
- <OverlayView />
<TimelineMenu />
<RichTextMenu />
<InkTranscription />
{this.snapLines}
<LightboxView key="lightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} />
- {/* <NewLightboxView key="newLightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} /> */}
+ <OverlayView />
+ <GPTPopup key="gptpopup" />
+ <GenerativeFill imageEditorOpen={ImageBox.imageEditorOpen} imageEditorSource={ImageBox.imageEditorSource} imageRootDoc={ImageBox.imageRootDoc} addDoc={ImageBox.addDoc} />
+ {/* <NewLightboxView key="newLightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} /> */}
</div>
);
}
diff --git a/src/client/views/OverlayView.scss b/src/client/views/OverlayView.scss
index 5362bf9f0..9b10d1cf7 100644
--- a/src/client/views/OverlayView.scss
+++ b/src/client/views/OverlayView.scss
@@ -4,6 +4,7 @@
top: 0;
width: 100vw;
height: 100vh;
+ z-index: 1001; // shouold be greater than LightboxView's z-index so that link lines and the presentation mini player appear
/* background-color: pink; */
}
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index e838473d2..7d65914b3 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -1,3 +1,4 @@
+
import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
import { computedFn } from 'mobx-utils';
@@ -7,14 +8,12 @@ import { Doc, DocListCast } from '../../fields/Doc';
import { Height, Width } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
import { NumCast } from '../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../Utils';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnTrue, setupMoveUpEvents, Utils } from '../../Utils';
import { DocumentType } from '../documents/DocumentTypes';
-import { DocumentManager } from '../util/DocumentManager';
import { DragManager } from '../util/DragManager';
import { Transform } from '../util/Transform';
import { CollectionFreeFormLinksView } from './collections/collectionFreeForm/CollectionFreeFormLinksView';
import { LightboxView } from './LightboxView';
-import { MainView } from './MainView';
import { DocumentView, DocumentViewInternal } from './nodes/DocumentView';
import './OverlayView.scss';
import { DefaultStyleProvider } from './StyleProvider';
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 46243d50a..a710555fa 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -109,10 +109,10 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
return Doc.toIcon(doc, isEmpty ? undefined : isOpen);
case StyleProp.TreeViewSortings:
const allSorts: { [key: string]: { color: string; icon: JSX.Element | string } | undefined } = {};
- allSorts[TreeSort.Down] = { color: Colors.MEDIUM_BLUE, icon: <BsArrowDown/> };
- allSorts[TreeSort.Up] = { color: 'crimson', icon: <BsArrowUp/> };
+ allSorts[TreeSort.AlphaDown] = { color: Colors.MEDIUM_BLUE, icon: <BsArrowDown/> };
+ allSorts[TreeSort.AlphaUp] = { color: 'crimson', icon: <BsArrowUp/> };
if (doc?._type_collection === CollectionViewType.Freeform) allSorts[TreeSort.Zindex] = { color: 'green', icon: 'Z' };
- allSorts[TreeSort.None] = { color: 'darkgray', icon: <BsArrowDownUp/> };
+ allSorts[TreeSort.WhenAdded] = { color: 'darkgray', icon: <BsArrowDownUp/> };
return allSorts;
case StyleProp.Highlighting:
if (doc && (Doc.IsSystem(doc) || doc.type === DocumentType.FONTICON)) return undefined;
@@ -166,8 +166,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
if (docColor) return docColor;
const docView = props?.DocumentView?.();
const backColor = backgroundCol() || docView?.props.styleProvider?.(docView.props.treeViewDoc, docView.props, StyleProp.BackgroundColor);
- if (!backColor) return undefined;
- return lightOrDark(backColor);
+ return backColor ? lightOrDark(backColor) : undefined;
case StyleProp.BorderRounding:
return StrCast(doc?.[fieldKey + 'borderRounding'], StrCast(doc?.layout_borderRounding, doc?._type_collection === CollectionViewType.Pile ? '50%' : ''));
case StyleProp.BorderPath:
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 0052c4196..e15d57306 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -498,6 +498,7 @@ export class CollectionDockingView extends CollectionSubView() {
_layout_fitWidth: true,
title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`,
});
+ Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd);
inheritParentAcls(this.rootDoc, docToAdd, false);
CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack);
}
@@ -541,6 +542,7 @@ export class CollectionDockingView extends CollectionSubView() {
_freeform_backgroundGrid: true,
title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`,
});
+ Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd);
inheritParentAcls(this.dataDoc, docToAdd, false);
CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack);
}
@@ -549,7 +551,7 @@ export class CollectionDockingView extends CollectionSubView() {
};
render() {
- const href = ImageCast(this.rootDoc.thumb)?.url.href;
+ const href = ImageCast(this.rootDoc.thumb)?.url?.href;
return this.props.renderDepth > -1 ? (
<div>
{href ? (
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index c189ef126..eb4685834 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -298,7 +298,11 @@ export function CollectionSubView<X>(moreProps?: X) {
let source = split;
if (split.startsWith('data:image') && split.includes('base64')) {
const [{ accessPaths }] = await Networking.PostToServer('/uploadRemoteImage', { sources: [split] });
- source = Utils.prepend(accessPaths.agnostic.client);
+ if (accessPaths.agnostic.client.indexOf("dashblobstore") === -1) {
+ source = Utils.prepend(accessPaths.agnostic.client);
+ } else {
+ source = accessPaths.agnostic.client;
+ }
}
if (source.startsWith('http')) {
const doc = Docs.Create.ImageDocument(source, { ...options, _width: 300 });
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index ea473d5cf..f379d09ff 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -137,7 +137,11 @@ export class TabDocView extends React.Component<TabDocViewProps> {
setupMoveUpEvents(
this,
e,
- e => !e.defaultPrevented && DragManager.StartDocumentDrag([iconWrap], new DragManager.DocumentDragData([doc], doc.dropAction as dropActionType), e.clientX, e.clientY),
+ e =>
+ !e.defaultPrevented &&
+ DragManager.StartDocumentDrag([iconWrap], new DragManager.DocumentDragData([doc], doc.dropAction as dropActionType), e.clientX, e.clientY, undefined, () => {
+ CollectionDockingView.CloseSplit(doc);
+ }),
returnFalse,
action(e => {
if (this.view) {
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index a3725be75..db33c46bb 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -78,10 +78,10 @@ const treeBulletWidth = function () {
};
export enum TreeSort {
- Up = 'up',
- Down = 'down',
- Zindex = 'z',
- None = 'none',
+ AlphaUp = 'alphabetical from z',
+ AlphaDown = 'alphabetical from A',
+ Zindex = 'by Z index',
+ WhenAdded = 'when added',
}
/**
* Renders a treeView of a collection of documents
@@ -584,7 +584,7 @@ export class TreeView extends React.Component<TreeViewProps> {
const expandKey = this.treeViewExpandedView;
const sortings = (this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) as { [key: string]: { color: string; icon: JSX.Element | string } }) ?? {};
if (['links', 'annotations', 'embeddings', this.fieldKey].includes(expandKey)) {
- const sorting = StrCast(this.doc.treeView_SortCriterion, TreeSort.None);
+ const sorting = StrCast(this.doc.treeView_SortCriterion, TreeSort.WhenAdded);
const sortKeys = Object.keys(sortings);
const curSortIndex = Math.max(
0,
@@ -624,10 +624,11 @@ export class TreeView extends React.Component<TreeViewProps> {
return (
<div>
{!docs?.length || this.props.AddToMap /* hack to identify pres box trees */ ? null : (
- <div className={'treeView-sorting'}>
+ <div className='treeView-sorting'>
<IconButton
color={sortings[sorting]?.color}
size={Size.XSMALL}
+ tooltip={`Sorted by : ${this.doc.treeView_SortCriterion}. click to cycle`}
icon={sortings[sorting]?.icon}
onPointerDown={e => {
downX = e.clientX;
@@ -646,8 +647,8 @@ export class TreeView extends React.Component<TreeViewProps> {
<ul
style={{ cursor: 'inherit' }}
key={expandKey + 'more'}
- title="click to change sort order"
- className={''} //this.doc.treeView_HideTitle ? 'no-indent' : ''}
+ title={`Sorted by : ${this.doc.treeView_SortCriterion}. click to cycle`}
+ className='' //this.doc.treeView_HideTitle ? 'no-indent' : ''}
onPointerDown={e => {
downX = e.clientX;
downY = e.clientY;
@@ -1134,7 +1135,7 @@ export class TreeView extends React.Component<TreeViewProps> {
};
@computed get renderBorder() {
- const sorting = StrCast(this.doc.treeView_SortCriterion, TreeSort.None);
+ const sorting = StrCast(this.doc.treeView_SortCriterion, TreeSort.WhenAdded);
const sortings = (this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) ?? {}) as { [key: string]: { color: string; label: string } };
return (
<div className={`treeView-border${this.props.treeView.outlineMode ? TreeViewType.outline : ''}`} style={{ borderColor: sortings[sorting]?.color }}>
@@ -1176,7 +1177,7 @@ export class TreeView extends React.Component<TreeViewProps> {
public static sortDocs(childDocs: Doc[], criterion: string | undefined) {
const docs = childDocs.slice();
- if (criterion !== TreeSort.None) {
+ if (criterion !== TreeSort.WhenAdded) {
const sortAlphaNum = (a: string, b: string): 0 | 1 | -1 => {
const reN = /[0-9]*$/;
const aA = a.replace(reN, '') ? a.replace(reN, '') : +a; // get rid of trailing numbers
@@ -1191,8 +1192,8 @@ export class TreeView extends React.Component<TreeViewProps> {
}
};
docs.sort(function (d1, d2): 0 | 1 | -1 {
- const a = criterion === TreeSort.Up ? d2 : d1;
- const b = criterion === TreeSort.Up ? d1 : d2;
+ const a = criterion === TreeSort.AlphaUp ? d2 : d1;
+ const b = criterion === TreeSort.AlphaUp ? d1 : d2;
const first = a[criterion === TreeSort.Zindex ? 'zIndex' : 'title'];
const second = b[criterion === TreeSort.Zindex ? 'zIndex' : 'title'];
if (typeof first === 'number' && typeof second === 'number') return first - second > 0 ? 1 : -1;
@@ -1243,7 +1244,7 @@ export class TreeView extends React.Component<TreeViewProps> {
childDocs = childDocs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result);
}
- const docs = TreeView.sortDocs(childDocs, StrCast(treeView_Parent.treeView_SortCriterion, TreeSort.None));
+ const docs = TreeView.sortDocs(childDocs, StrCast(treeView_Parent.treeView_SortCriterion, TreeSort.WhenAdded));
const rowWidth = () => panelWidth() - treeBulletWidth() * (treeView.props.NativeDimScaling?.() || 1);
const treeView_Refs = new Map<Doc, TreeView | undefined>();
return docs
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index fb8ec93b2..89deb733a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -21,6 +21,8 @@ export interface CollectionFreeFormLinkViewProps {
LinkDocs: Doc[];
}
+// props.screentolocatransform
+
@observer
export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFormLinkViewProps> {
@observable _opacity: number = 0;
@@ -59,7 +61,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
0
); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render()
setTimeout(
- action(() => (!LinkDocs.length || !linkDoc.link_displayLine) && (this._opacity = 0.05)),
+ action(() => (!LinkDocs.length || !(linkDoc.link_displayLine || Doc.UserDoc().showLinkLines)) && (this._opacity = 0.05)),
750
); // this will unhighlight the link line.
const a = A.ContentDiv.getBoundingClientRect();
@@ -235,11 +237,12 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
bActive,
textX,
textY,
- // pt1,
- // pt2,
+ // fully connected
+ pt1,
+ pt2,
// this code adds space between links
- pt1: [pt1[0] + pt1normalized[0] * 13, pt1[1] + pt1normalized[1] * 13],
- pt2: [pt2[0] + pt2normalized[0] * 13, pt2[1] + pt2normalized[1] * 13],
+ // pt1: [pt1[0] + pt1normalized[0] * 13, pt1[1] + pt1normalized[1] * 13],
+ // pt2: [pt2[0] + pt2normalized[0] * 13, pt2[1] + pt2normalized[1] * 13],
};
}
@@ -269,7 +272,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
link.link_displayArrow = false;
}
- return link.opacity === 0 || !a.width || !b.width || (!link.link_displayLine && !aActive && !bActive) ? null : (
+ return link.opacity === 0 || !a.width || !b.width || (!(Doc.UserDoc().showLinkLines || link.link_displayLine) && !aActive && !bActive) ? null : (
<>
<defs>
<marker id={`${link[Id] + 'arrowhead'}`} markerWidth="4" markerHeight="3" refX="0" refY="1.5" orient="auto">
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index 420e6a318..7c869af24 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -12,7 +12,10 @@ export class CollectionFreeFormLinksView extends React.Component {
@computed get uniqueConnections() {
return Array.from(new Set(DocumentManager.Instance.LinkedDocumentViews))
.filter(c => !LightboxView.LightboxDoc || (LightboxView.IsLightboxDocView(c.a.docViewPath) && LightboxView.IsLightboxDocView(c.b.docViewPath)))
- .map(c => <CollectionFreeFormLinkView key={c.l[Id]} A={c.a} B={c.b} LinkDocs={[c.l]} />);
+ .map(c => {
+ console.log("got a connectoin", c)
+ return <CollectionFreeFormLinkView key={c.l[Id]} A={c.a} B={c.b} LinkDocs={[c.l]} />;
+ });
}
render() {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index ffcf0999c..f5cc1eb53 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -237,7 +237,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick);
onChildDoubleClickHandler = () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
elementFunc = () => this._layoutElements;
- shrinkWrap = () => {
+ fitContentOnce = () => {
if (this.props.DocumentView?.().nativeWidth) return;
const vals = this.fitToContentVals;
this.layoutDoc._freeform_panX = vals.bounds.cx;
@@ -1595,6 +1595,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
elements => (this._layoutElements = elements || []),
{ fireImmediately: true, name: 'doLayout' }
);
+
+ this._disposers.fitContent = reaction(
+ () => this.rootDoc.fitContentOnce,
+ fitContentOnce => {
+ if (fitContentOnce) this.fitContentOnce();
+ this.rootDoc.fitContentOnce = undefined;
+ },
+ { fireImmediately: true, name: 'fitContent' }
+ );
})
);
}
@@ -1785,6 +1794,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
!Doc.noviceMode &&
optionItems.push({ description: (this._showAnimTimeline ? 'Close' : 'Open') + ' Animation Timeline', event: action(() => (this._showAnimTimeline = !this._showAnimTimeline)), icon: 'eye' });
this.props.renderDepth && optionItems.push({ description: 'Use Background Color as Default', event: () => (Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor)), icon: 'palette' });
+ this.props.renderDepth &&
+ optionItems.push({
+ description: 'Fit Content Once',
+ event: () => {
+ this.fitContentOnce();
+ },
+ icon: 'object-group',
+ });
if (!Doc.noviceMode) {
optionItems.push({ description: (!Doc.NativeWidth(this.layoutDoc) || !Doc.NativeHeight(this.layoutDoc) ? 'Freeze' : 'Unfreeze') + ' Aspect', event: this.toggleNativeDimensions, icon: 'snowflake' });
}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss
index f87a06033..cb0d5e03f 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss
@@ -9,6 +9,12 @@
flex-direction: column;
width: 100%;
align-items: center;
+ position: relative;
+ > .iconButton-container {
+ top: 0;
+ left: 0;
+ position: absolute;
+ }
.contentFittingDocumentView {
width: unset;
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 10532b9d9..80da4e1a2 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -1,3 +1,5 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Button } from 'browndash-components';
import { action, computed } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -6,7 +8,7 @@ import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types
import { returnFalse } from '../../../../Utils';
import { DragManager, dropActionType } from '../../../util/DragManager';
import { Transform } from '../../../util/Transform';
-import { undoBatch } from '../../../util/UndoManager';
+import { undoable, undoBatch } from '../../../util/UndoManager';
import { DocumentView } from '../../nodes/DocumentView';
import { CollectionSubView } from '../CollectionSubView';
import './CollectionMulticolumnView.scss';
@@ -301,6 +303,9 @@ export class CollectionMulticolumnView extends CollectionSubView() {
collector.push(
<div className="document-wrapper" key={'wrapper' + i} style={{ width: width() }}>
{this.getDisplayDoc(layout, dxf, docwidth, docheight, shouldNotScale)}
+ <Button icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} onClick={undoable(e => {
+ this.props.removeDocument?.(layout);
+ }, "close doc")} color={StrCast(Doc.UserDoc().userColor)} />
<WidthLabel layout={layout} collectionDoc={Document} />
</div>,
<ResizeBar
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 256377758..38bf1042d 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -85,10 +85,10 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log('[FontIconBox.tsx] toggleOverlay failed');
});
-ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll', checkResult?: boolean) {
+ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'viewAllPersist', checkResult?: boolean) {
const selected = SelectionManager.Docs().lastElement();
// prettier-ignore
- const map: Map<'flashcards' | 'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc) => void;}> = new Map([
+ const map: Map<'flashcards' | 'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll' | 'viewAllPersist', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc) => void;}> = new Map([
['grid', {
checkResult: (doc:Doc) => BoolCast(doc._freeform_backgroundGrid, false),
setDoc: (doc:Doc) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid,
@@ -101,6 +101,10 @@ ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'grid' | 'snapli
checkResult: (doc:Doc) => BoolCast(doc._freeform_fitContentsToBox, false),
setDoc: (doc:Doc) => doc._freeform_fitContentsToBox = !doc._freeform_fitContentsToBox,
}],
+ ['viewAllPersist', {
+ checkResult: (doc:Doc) => false,
+ setDoc: (doc:Doc) => doc.fitContentOnce = true
+ }],
['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),
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 39c8d3348..533a047b1 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -993,7 +993,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
// the small blue dots that mark the endpoints of links
TraceMobx();
if (this.props.hideLinkAnchors || this.layoutDoc.layout_hideLinkAnchors || this.props.dontRegisterView || this.layoutDoc.layout_unrendered) return null;
- const filtered = DocUtils.FilterDocs(this.directLinks, this.props.childFilters?.() ?? [], []).filter(d => d.link_displayLine);
+ const filtered = DocUtils.FilterDocs(this.directLinks, this.props.childFilters?.() ?? [], []).filter(d => d.link_displayLine || Doc.UserDoc().showLinkLines);
return filtered.map(link => (
<div className="documentView-anchorCont" key={link[Id]}>
<DocumentView
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index 41ad90155..286b80426 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -1,12 +1,13 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Button, MultiToggle, ColorPicker, Dropdown, DropdownType, EditableText, IconButton, IListItemProps, NumberDropdown, NumberDropdownType, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components';
+import { Button, ColorPicker, Dropdown, DropdownType, EditableText, IconButton, IListItemProps, MultiToggle, NumberDropdown, NumberDropdownType, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components';
import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc';
import { ScriptField } from '../../../../fields/ScriptField';
import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
+import { Utils } from '../../../../Utils';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
import { SelectionManager } from '../../../util/SelectionManager';
import { undoable, UndoManager } from '../../../util/UndoManager';
@@ -14,14 +15,12 @@ import { ContextMenu } from '../../ContextMenu';
import { DocComponent } from '../../DocComponent';
import { EditableView } from '../../EditableView';
import { Colors } from '../../global/globalEnums';
+import { SelectedDocView } from '../../selectedDoc';
import { StyleProp } from '../../StyleProvider';
-import { FieldView, FieldViewProps } from '../FieldView';
import { OpenWhere } from '../DocumentView';
+import { FieldView, FieldViewProps } from '../FieldView';
import { RichTextMenu } from '../formattedText/RichTextMenu';
import './FontIconBox.scss';
-import { SelectedDocView } from '../../selectedDoc';
-import { Utils } from '../../../../Utils';
-import { FaAlignCenter, FaAlignJustify, FaAlignLeft, FaAlignRight } from 'react-icons/fa';
export enum ButtonType {
TextButton = 'textBtn',
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 44da98f75..2c8e97512 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -36,7 +36,6 @@ import { FieldView, FieldViewProps } from './FieldView';
import './ImageBox.scss';
import { PinProps, PresBox } from './trails';
import React = require('react');
-import Color = require('color');
export const pageSchema = createSchema({
googlePhotosUrl: 'string',
@@ -55,6 +54,13 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(ImageBox, fieldKey);
}
+
+ @observable public static imageRootDoc: Doc | undefined;
+ @observable public static imageEditorOpen: boolean = false;
+ @observable public static imageEditorSource: string = '';
+ @observable public static addDoc: ((doc: Doc | Doc[], annotationKey?: string) => boolean) | undefined;
+ @action public static setImageEditorOpen(open: boolean) {ImageBox.imageEditorOpen = open;}
+ @action public static setImageEditorSource(source: string) {ImageBox.imageEditorSource = source;}
private _ignoreScroll = false;
private _forcedScroll = false;
private _dropDisposer?: DragManager.DragDropDisposer;
@@ -246,6 +252,16 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
funcs.push({ description: `Show ${this.layoutDoc._showFullRes ? 'Dynamic Res' : 'Full Res'}`, event: this.resolution, icon: 'expand' });
funcs.push({ description: 'Set Native Pixel Size', event: this.setNativeSize, icon: 'expand-arrows-alt' });
funcs.push({ description: 'Copy path', event: () => Utils.CopyText(this.choosePath(field.url)), icon: 'copy' });
+ funcs.push({
+ description: 'Open Image Editor',
+ event: action(() => {
+ ImageBox.setImageEditorOpen(true);
+ ImageBox.setImageEditorSource(this.choosePath(field.url));
+ ImageBox.addDoc = this.props.addDocument;
+ ImageBox.imageRootDoc = this.rootDoc;
+ }),
+ icon: 'pencil-alt',
+ });
if (!Doc.noviceMode) {
funcs.push({ description: 'Export to Google Photos', event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: 'caret-square-right' });
@@ -287,10 +303,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
@computed private get url() {
const data = Cast(this.dataDoc[this.fieldKey], ImageField);
- return data ? data.url.href : undefined;
+ return data ? data.url?.href : undefined;
}
choosePath(url: URL) {
+ if (!url?.href) return "";
const lower = url.href.toLowerCase();
if (url.protocol === 'data') return url.href;
if (url.href.indexOf(window.location.origin) === -1 && url.href.indexOf('dashblobstore') === -1) return Utils.CorsProxy(url.href);
@@ -318,7 +335,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
if (!(data instanceof ImageField)) {
return null;
}
- const primary = data.url.href;
+ const primary = data.url?.href;
if (primary.includes(window.location.origin)) {
return null;
}
diff --git a/src/client/views/nodes/WebBoxRenderer.js b/src/client/views/nodes/WebBoxRenderer.js
index eb8064780..425ef3e54 100644
--- a/src/client/views/nodes/WebBoxRenderer.js
+++ b/src/client/views/nodes/WebBoxRenderer.js
@@ -42,21 +42,21 @@ var ForeignHtmlRenderer = function (styleSheets) {
url = CorsProxy(new URL(webUrl).origin + inurl);
} else if (!inurl.startsWith('http') && !inurl.startsWith('//')) {
url = CorsProxy(webUrl + '/' + inurl);
- } else if (inurl.startsWith('https')) {
+ } else if (inurl.startsWith('https') && !inurl.startsWith(window.location.origin)) {
url = CorsProxy(inurl);
}
xhr.open('GET', url);
xhr.responseType = 'blob';
xhr.onreadystatechange = async function () {
- if (xhr.readyState === 4 && xhr.status === 200) {
+ if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
const resBase64 = await binaryStringToBase64(xhr.response);
resolve({
resourceUrl: inurl,
resourceBase64: resBase64,
});
- } else if (xhr.readyState === 4) {
+ } else if (xhr.readyState === XMLHttpRequest.DONE) {
console.log("COULDN'T FIND: " + (inurl.startsWith('/') ? webUrl + inurl : inurl));
resolve({
resourceUrl: '',
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 2afbbb457..da277826a 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -70,6 +70,7 @@ import { schema } from './schema_rts';
import { SummaryView } from './SummaryView';
import applyDevTools = require('prosemirror-dev-tools');
import React = require('react');
+import { GPTPopup, GPTPopupMode } from '../../pdf/GPTPopup/GPTPopup';
const translateGoogleApi = require('translate-google-api');
export const GoogleRef = 'googleDocId';
type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
@@ -903,11 +904,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
this._downX = this._downY = Number.NaN;
};
- animateRes = (resIndex: number) => {
- if (resIndex < this.gptRes.length) {
- this.dataDoc.text = (this.dataDoc.text as RichTextField)?.Text + this.gptRes[resIndex];
+ animateRes = (resIndex: number, newText: string) => {
+ if (resIndex < newText.length) {
+ const marks = this._editorView?.state.storedMarks ?? [];
+ this._editorView?.dispatch(this._editorView.state.tr.setStoredMarks(marks).insertText(newText[resIndex]).setStoredMarks(marks));
setTimeout(() => {
- this.animateRes(resIndex + 1);
+ this.animateRes(resIndex + 1, newText);
}, 20);
}
};
@@ -915,45 +917,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
askGPT = action(async () => {
try {
let res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION);
- if (res) {
- this.gptRes = res;
- this.animateRes(0);
+ if (!res) {
+ console.error('GPT call failed');
+ this.animateRes(0, 'Something went wrong.');
+ } else {
+ this.animateRes(0, res);
}
} catch (err) {
- console.log(err);
- this.dataDoc.text = (this.dataDoc.text as RichTextField)?.Text + 'Something went wrong';
+ console.error('GPT call failed');
+ this.animateRes(0, 'Something went wrong.');
}
});
generateImage = async () => {
console.log('Generate image from text: ', (this.dataDoc.text as RichTextField)?.Text);
- try {
- let image_url = await gptImageCall((this.dataDoc.text as RichTextField)?.Text);
- if (image_url) {
- const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [image_url] });
- const source = result.accessPaths.agnostic.client;
- const newDoc = Docs.Create.ImageDocument(source, {
- x: NumCast(this.rootDoc.x) + NumCast(this.layoutDoc._width) + 10,
- y: NumCast(this.rootDoc.y),
- _height: 200,
- _width: 200,
- data_nativeWidth: result.nativeWidth,
- data_nativeHeight: result.nativeHeight,
- });
- if (Doc.IsInMyOverlay(this.rootDoc)) {
- newDoc.overlayX = this.rootDoc.x;
- newDoc.overlayY = NumCast(this.rootDoc.y) + NumCast(this.rootDoc._height);
- Doc.AddToMyOverlay(newDoc);
- } else {
- this.props.addDocument?.(newDoc);
- }
- // Create link between prompt and image
- DocUtils.MakeLink(this.rootDoc, newDoc, { link_relationship: 'Image Prompt' });
- }
- } catch (err) {
- console.log(err);
- return '';
- }
+ GPTPopup.Instance?.setTextAnchor(this.getAnchor(false));
+ GPTPopup.Instance?.setImgTargetDoc(this.rootDoc);
+ GPTPopup.Instance.addToCollection = this.props.addDocument;
+ GPTPopup.Instance.setImgDesc((this.dataDoc.text as RichTextField)?.Text);
+ GPTPopup.Instance.generateImage();
};
breakupDictation = () => {
@@ -1248,11 +1230,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props);
setTimeout(this.autoLink, 20);
}
- // Accessing editor and text doc for gpt assisted text edits
- if (this._editorView && selected) {
- AnchorMenu.Instance?.setEditorView(this._editorView);
- AnchorMenu.Instance?.setTextDoc(this.dataDoc);
- }
}),
{ fireImmediately: true }
);
diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.scss b/src/client/views/nodes/generativeFill/GenerativeFill.scss
new file mode 100644
index 000000000..c2669a950
--- /dev/null
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.scss
@@ -0,0 +1,97 @@
+$navHeight: 5rem;
+$canvasSize: 1024px;
+$scale: 0.5;
+
+.generativeFillContainer {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 9999;
+ height: 100vh;
+ width: 100vw;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+
+ .generativeFillControls {
+ flex-shrink: 0;
+ height: $navHeight;
+ color: #000000;
+ background-color: #ffffff;
+ z-index: 999;
+ width: 100%;
+ display: flex;
+ gap: 3rem;
+ justify-content: space-between;
+ align-items: center;
+ border-bottom: 1px solid #c7cdd0;
+ padding: 0 2rem;
+
+ h1 {
+ font-size: 1.5rem;
+ }
+ }
+
+ .drawingArea {
+ cursor: none;
+ touch-action: none;
+ position: relative;
+ flex-grow: 1;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 100%;
+ background-color: #f0f4f6;
+
+ canvas {
+ display: block;
+ position: absolute;
+ transform-origin: 50% 50%;
+ }
+
+ .pointer {
+ pointer-events: none;
+ position: absolute;
+ border-radius: 50%;
+ width: 50px;
+ height: 50px;
+ border: 1px solid #ffffff;
+ transform: translate(-50%, -50%);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ .innerPointer {
+ width: 100%;
+ height: 100%;
+ border: 1px solid #000000;
+ border-radius: 50%;
+ }
+ }
+
+ .iconContainer {
+ position: absolute;
+ top: 2rem;
+ left: 2rem;
+ display: flex;
+ flex-direction: column;
+ gap: 2rem;
+ }
+
+ .editsBox {
+ position: absolute;
+ top: 2rem;
+ right: 2rem;
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+
+ img {
+ transition: all 0.2s ease-in-out;
+ &:hover {
+ opacity: 0.8;
+ }
+ }
+ }
+ }
+}
diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
new file mode 100644
index 000000000..9c03600cf
--- /dev/null
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
@@ -0,0 +1,584 @@
+import './GenerativeFill.scss';
+import React = require('react');
+import { useEffect, useRef, useState } from 'react';
+import { APISuccess, ImageUtility } from './generativeFillUtils/ImageHandler';
+import { BrushHandler } from './generativeFillUtils/BrushHandler';
+import { IconButton } from 'browndash-components';
+import { Checkbox, FormControlLabel, Slider, TextField } from '@mui/material';
+import { CursorData, ImageDimensions, Point } from './generativeFillUtils/generativeFillInterfaces';
+import { activeColor, canvasSize, eraserColor, freeformRenderSize, newCollectionSize, offsetDistanceY, offsetX } from './generativeFillUtils/generativeFillConstants';
+import { PointerHandler } from './generativeFillUtils/PointerHandler';
+import { IoMdUndo, IoMdRedo } from 'react-icons/io';
+import { MainView } from '../../MainView';
+import { Doc, DocListCast } from '../../../../fields/Doc';
+import { Networking } from '../../../Network';
+import { Utils } from '../../../../Utils';
+import { DocUtils, Docs } from '../../../documents/Documents';
+import { NumCast } from '../../../../fields/Types';
+import { CollectionDockingView } from '../../collections/CollectionDockingView';
+import { OpenWhereMod } from '../DocumentView';
+import Buttons from './GenerativeFillButtons';
+import { List } from '../../../../fields/List';
+import { CgClose } from 'react-icons/cg';
+import { ImageBox } from '../ImageBox';
+
+enum BrushStyle {
+ ADD,
+ SUBTRACT,
+ MARQUEE,
+}
+
+interface GenerativeFillProps {
+ imageEditorOpen: boolean;
+ imageEditorSource: string;
+ imageRootDoc: Doc | undefined;
+ addDoc: ((doc: Doc | Doc[], annotationKey?: string) => boolean) | undefined;
+}
+
+// Added field on image doc: gen_fill_children: List of children Docs
+
+const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc }: GenerativeFillProps) => {
+ const canvasRef = useRef<HTMLCanvasElement>(null);
+ const canvasBackgroundRef = useRef<HTMLCanvasElement>(null);
+ const drawingAreaRef = useRef<HTMLDivElement>(null);
+ const [cursorData, setCursorData] = useState<CursorData>({
+ x: 0,
+ y: 0,
+ width: 150,
+ });
+ const [isBrushing, setIsBrushing] = useState(false);
+ const [canvasScale, setCanvasScale] = useState(0.5);
+ // format: array of [image source, corresponding image Doc]
+ const [edits, setEdits] = useState<(string | Doc)[][]>([]);
+ const [edited, setEdited] = useState(false);
+ const [brushStyle, setBrushStyle] = useState<BrushStyle>(BrushStyle.ADD);
+ const [input, setInput] = useState('');
+ const [loading, setLoading] = useState(false);
+ const [canvasDims, setCanvasDims] = useState<ImageDimensions>({
+ width: canvasSize,
+ height: canvasSize,
+ });
+ // whether to create a new collection or not
+ const [isNewCollection, setIsNewCollection] = useState(true);
+ // the current image in the main canvas
+ const currImg = useRef<HTMLImageElement | null>(null);
+ // the unedited version of each generation (parent)
+ const originalImg = useRef<HTMLImageElement | null>(null);
+ const originalDoc = useRef<Doc | null>(null);
+ // stores history of data urls
+ const undoStack = useRef<string[]>([]);
+ // stores redo stack
+ const redoStack = useRef<string[]>([]);
+
+ // references to keep track of tree structure
+ const newCollectionRef = useRef<Doc | null>(null);
+ const parentDoc = useRef<Doc | null>(null);
+ const childrenDocs = useRef<Doc[]>([]);
+
+ // Undo and Redo
+ const handleUndo = () => {
+ const ctx = ImageUtility.getCanvasContext(canvasRef);
+ if (!ctx || !currImg.current || !canvasRef.current) return;
+
+ const target = undoStack.current[undoStack.current.length - 1];
+ if (!target) {
+ ImageUtility.drawImgToCanvas(currImg.current, canvasRef, canvasDims.width, canvasDims.height);
+ } else {
+ redoStack.current = [...redoStack.current, canvasRef.current.toDataURL()];
+ const img = new Image();
+ img.src = target;
+ ImageUtility.drawImgToCanvas(img, canvasRef, canvasDims.width, canvasDims.height);
+ undoStack.current = undoStack.current.slice(0, -1);
+ }
+ };
+
+ const handleRedo = () => {
+ const ctx = ImageUtility.getCanvasContext(canvasRef);
+ if (!ctx || !currImg.current || !canvasRef.current) return;
+
+ const target = redoStack.current[redoStack.current.length - 1];
+ if (!target) {
+ } else {
+ undoStack.current = [...undoStack.current, canvasRef.current?.toDataURL()];
+ const img = new Image();
+ img.src = target;
+ ImageUtility.drawImgToCanvas(img, canvasRef, canvasDims.width, canvasDims.height);
+ redoStack.current = redoStack.current.slice(0, -1);
+ }
+ };
+
+ // resets any erase strokes
+ const handleReset = () => {
+ if (!canvasRef.current || !currImg.current) return;
+ const ctx = ImageUtility.getCanvasContext(canvasRef);
+ if (!ctx) return;
+ ctx.clearRect(0, 0, canvasSize, canvasSize);
+ undoStack.current = [];
+ redoStack.current = [];
+ ImageUtility.drawImgToCanvas(currImg.current, canvasRef, canvasDims.width, canvasDims.height);
+ };
+
+ // initiate brushing
+ const handlePointerDown = (e: React.PointerEvent) => {
+ const canvas = canvasRef.current;
+ if (!canvas) return;
+ const ctx = ImageUtility.getCanvasContext(canvasRef);
+ if (!ctx) return;
+
+ undoStack.current = [...undoStack.current, canvasRef.current.toDataURL()];
+ redoStack.current = [];
+
+ setIsBrushing(true);
+ const { x, y } = PointerHandler.getPointRelativeToElement(canvas, e, canvasScale);
+ BrushHandler.brushCircleOverlay(x, y, cursorData.width / 2 / canvasScale, ctx, eraserColor, brushStyle === BrushStyle.SUBTRACT);
+ };
+
+ // stop brushing, push to undo stack
+ const handlePointerUp = (e: React.PointerEvent) => {
+ const ctx = ImageUtility.getCanvasContext(canvasBackgroundRef);
+ if (!ctx) return;
+ if (!isBrushing) return;
+ setIsBrushing(false);
+ };
+
+ // handles brushing on pointer movement
+ useEffect(() => {
+ if (!isBrushing) return;
+ const canvas = canvasRef.current;
+ if (!canvas) return;
+ const ctx = ImageUtility.getCanvasContext(canvasRef);
+ if (!ctx) return;
+
+ const handlePointerMove = (e: PointerEvent) => {
+ const currPoint = PointerHandler.getPointRelativeToElement(canvas, e, canvasScale);
+ const lastPoint: Point = {
+ x: currPoint.x - e.movementX / canvasScale,
+ y: currPoint.y - e.movementY / canvasScale,
+ };
+ BrushHandler.createBrushPathOverlay(lastPoint, currPoint, cursorData.width / 2 / canvasScale, ctx, eraserColor, brushStyle === BrushStyle.SUBTRACT);
+ };
+
+ drawingAreaRef.current?.addEventListener('pointermove', handlePointerMove);
+ return () => {
+ drawingAreaRef.current?.removeEventListener('pointermove', handlePointerMove);
+ };
+ }, [isBrushing]);
+
+ // first load
+ useEffect(() => {
+ if (!imageEditorSource || imageEditorSource === '') return;
+ const img = new Image();
+ img.src = imageEditorSource;
+ currImg.current = img;
+ originalImg.current = img;
+ img.onload = () => {
+ const imgWidth = img.naturalWidth;
+ const imgHeight = img.naturalHeight;
+ const scale = Math.min(canvasSize / imgWidth, canvasSize / imgHeight);
+ const width = imgWidth * scale;
+ const height = imgHeight * scale;
+ setCanvasDims({ width, height });
+ };
+
+ // cleanup
+ return () => {
+ setInput('');
+ setEdited(false);
+ newCollectionRef.current = null;
+ parentDoc.current = null;
+ childrenDocs.current = [];
+ currImg.current = null;
+ originalImg.current = null;
+ originalDoc.current = null;
+ undoStack.current = [];
+ redoStack.current = [];
+ ImageUtility.clearCanvas(canvasRef);
+ };
+ }, [canvasRef, imageEditorSource]);
+
+ // once the appropriate dimensions are set, draw the image to the canvas
+ useEffect(() => {
+ if (!currImg.current) return;
+ ImageUtility.drawImgToCanvas(currImg.current, canvasRef, canvasDims.width, canvasDims.height);
+ }, [canvasDims]);
+
+ // handles brush sizing
+ useEffect(() => {
+ const handleKeyPress = (e: KeyboardEvent) => {
+ if (e.key === 'ArrowUp') {
+ e.preventDefault();
+ e.stopPropagation();
+ setCursorData(data => ({ ...data, width: data.width + 5 }));
+ } else if (e.key === 'ArrowDown') {
+ e.preventDefault();
+ e.stopPropagation();
+ setCursorData(data => (data.width >= 20 ? { ...data, width: data.width - 5 } : data));
+ }
+ };
+ window.addEventListener('keydown', handleKeyPress);
+ return () => window.removeEventListener('keydown', handleKeyPress);
+ }, []);
+
+ // handle pinch zoom
+ useEffect(() => {
+ const handlePinch = (e: WheelEvent) => {
+ e.preventDefault();
+ e.stopPropagation();
+ const delta = e.deltaY;
+ const scaleFactor = delta > 0 ? 0.98 : 1.02;
+ setCanvasScale(prevScale => prevScale * scaleFactor);
+ };
+
+ drawingAreaRef.current?.addEventListener('wheel', handlePinch, {
+ passive: false,
+ });
+ return () => drawingAreaRef.current?.removeEventListener('wheel', handlePinch);
+ }, [drawingAreaRef]);
+
+ // updates the current position of the cursor
+ const updateCursorData = (e: React.PointerEvent) => {
+ const drawingArea = drawingAreaRef.current;
+ if (!drawingArea) return;
+ const { x, y } = PointerHandler.getPointRelativeToElement(drawingArea, e, 1);
+ setCursorData(data => ({
+ ...data,
+ x,
+ y,
+ }));
+ };
+
+ // Get AI Edit
+ const getEdit = async () => {
+ const img = currImg.current;
+ if (!img) return;
+ const canvas = canvasRef.current;
+ if (!canvas) return;
+ const ctx = ImageUtility.getCanvasContext(canvasRef);
+ if (!ctx) return;
+ setLoading(true);
+ setEdited(true);
+ try {
+ const canvasOriginalImg = ImageUtility.getCanvasImg(img);
+ if (!canvasOriginalImg) return;
+ const canvasMask = ImageUtility.getCanvasMask(canvas, canvasOriginalImg);
+ if (!canvasMask) return;
+ const maskBlob = await ImageUtility.canvasToBlob(canvasMask);
+ const imgBlob = await ImageUtility.canvasToBlob(canvasOriginalImg);
+ const res = await ImageUtility.getEdit(imgBlob, maskBlob, input !== '' ? input + ' in the same style' : 'Fill in the image in the same style', 2);
+ // const res = await ImageUtility.mockGetEdit(img.src);
+
+ // create first image
+ if (!newCollectionRef.current) {
+ if (!isNewCollection && imageRootDoc) {
+ // if the parent hasn't been set yet
+ if (!parentDoc.current) parentDoc.current = imageRootDoc;
+ } else {
+ if (!(originalImg.current && imageRootDoc)) return;
+ // create new collection and add it to the view
+ newCollectionRef.current = Docs.Create.FreeformDocument([], {
+ x: NumCast(imageRootDoc.x) + NumCast(imageRootDoc._width) + offsetX,
+ y: NumCast(imageRootDoc.y),
+ _width: newCollectionSize,
+ _height: newCollectionSize,
+ title: 'Image edit collection',
+ });
+ DocUtils.MakeLink(imageRootDoc, newCollectionRef.current, { link_relationship: 'Image Edit Version History', link_displayLine: false });
+
+ // opening new tab
+ CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right);
+
+ // add the doc to the main freeform
+ await createNewImgDoc(originalImg.current, true);
+ }
+ } else {
+ childrenDocs.current = [];
+ }
+
+ originalImg.current = currImg.current;
+ originalDoc.current = parentDoc.current;
+ const { urls } = res as APISuccess;
+ const imgUrls = await Promise.all(urls.map(url => ImageUtility.convertImgToCanvasUrl(url, canvasDims.width, canvasDims.height)));
+ const imgRes = await Promise.all(
+ imgUrls.map(async url => {
+ const saveRes = await onSave(url);
+ return [url, saveRes as Doc];
+ })
+ );
+ setEdits(imgRes);
+ const image = new Image();
+ image.src = imgUrls[0];
+ ImageUtility.drawImgToCanvas(image, canvasRef, canvasDims.width, canvasDims.height);
+ currImg.current = image;
+ parentDoc.current = imgRes[0][1] as Doc;
+ } catch (err) {
+ console.log(err);
+ }
+ setLoading(false);
+ };
+
+ // adjusts all the img positions to be aligned
+ const adjustImgPositions = () => {
+ if (!parentDoc.current) return;
+ const startY = NumCast(parentDoc.current.y);
+ const children = DocListCast(parentDoc.current.gen_fill_children);
+ const len = children.length;
+ let initialYPositions: number[] = [];
+ for (let i = 0; i < len; i++) {
+ initialYPositions.push(startY + i * offsetDistanceY);
+ }
+ children.forEach((doc, i) => {
+ if (len % 2 === 1) {
+ doc.y = initialYPositions[i] - Math.floor(len / 2) * offsetDistanceY;
+ } else {
+ doc.y = initialYPositions[i] - (len / 2 - 1 / 2) * offsetDistanceY;
+ }
+ });
+ };
+
+ // creates a new image document and returns its reference
+ const createNewImgDoc = async (img: HTMLImageElement, firstDoc: boolean): Promise<Doc | undefined> => {
+ if (!imageRootDoc) return;
+ const src = img.src;
+ const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [src] });
+ const source = Utils.prepend(result.accessPaths.agnostic.client);
+
+ if (firstDoc) {
+ const x = 0;
+ const initialY = 0;
+ const newImg = Docs.Create.ImageDocument(source, {
+ x: x,
+ y: initialY,
+ _height: freeformRenderSize,
+ _width: freeformRenderSize,
+ data_nativeWidth: result.nativeWidth,
+ data_nativeHeight: result.nativeHeight,
+ });
+ if (isNewCollection && newCollectionRef.current) {
+ Doc.AddDocToList(newCollectionRef.current, undefined, newImg);
+ } else {
+ addDoc?.(newImg);
+ }
+ parentDoc.current = newImg;
+ return newImg;
+ } else {
+ if (!parentDoc.current) return;
+ const x = NumCast(parentDoc.current.x) + freeformRenderSize + offsetX;
+ const initialY = 0;
+
+ const newImg = Docs.Create.ImageDocument(source, {
+ x: x,
+ y: initialY,
+ _height: freeformRenderSize,
+ _width: freeformRenderSize,
+ data_nativeWidth: result.nativeWidth,
+ data_nativeHeight: result.nativeHeight,
+ });
+
+ const parentList = DocListCast(parentDoc.current.gen_fill_children);
+ if (parentList.length > 0) {
+ parentList.push(newImg);
+ parentDoc.current.gen_fill_children = new List<Doc>(parentList);
+ } else {
+ parentDoc.current.gen_fill_children = new List<Doc>([newImg]);
+ }
+
+ DocUtils.MakeLink(parentDoc.current, newImg, { link_relationship: `Image edit; Prompt: ${input}`, link_displayLine: true });
+ adjustImgPositions();
+
+ if (isNewCollection && newCollectionRef.current) {
+ Doc.AddDocToList(newCollectionRef.current, undefined, newImg);
+ } else {
+ addDoc?.(newImg);
+ }
+ return newImg;
+ }
+ };
+
+ // Saves an image to the collection
+ const onSave = async (src: string) => {
+ const img = new Image();
+ img.src = src;
+ if (!currImg.current || !originalImg.current || !imageRootDoc) return;
+ try {
+ const res = await createNewImgDoc(img, false);
+ return res;
+ } catch (err) {
+ console.log(err);
+ }
+ };
+
+ // Closes the editor view
+ const handleViewClose = () => {
+ ImageBox.setImageEditorOpen(false);
+ ImageBox.setImageEditorSource('');
+ if (newCollectionRef.current) {
+ newCollectionRef.current.fitContentOnce = true;
+ }
+ setEdits([]);
+ };
+
+ return (
+ <div className="generativeFillContainer" style={{ display: imageEditorOpen ? 'flex' : 'none' }}>
+ <div className="generativeFillControls">
+ <h1>Image Editor</h1>
+ <div style={{ display: 'flex', alignItems: 'center', gap: '1.5rem' }}>
+ <FormControlLabel
+ control={
+ <Checkbox
+ // disable once edited has been clicked (doesn't make sense to change after first edit)
+ disabled={edited}
+ checked={isNewCollection}
+ onChange={e => {
+ setIsNewCollection(prev => !prev);
+ }}
+ />
+ }
+ label={'Create New Collection'}
+ labelPlacement="end"
+ sx={{ whiteSpace: 'nowrap' }}
+ />
+ <Buttons getEdit={getEdit} loading={loading} onReset={handleReset} />
+ <IconButton color={activeColor} tooltip="close" icon={<CgClose size={'16px'} />} onClick={handleViewClose} />
+ </div>
+ </div>
+ {/* Main canvas for editing */}
+ <div
+ className="drawingArea" // this only works if pointerevents: none is set on the custom pointer
+ ref={drawingAreaRef}
+ onPointerOver={updateCursorData}
+ onPointerMove={updateCursorData}
+ onPointerDown={handlePointerDown}
+ onPointerUp={handlePointerUp}>
+ <canvas ref={canvasRef} width={canvasDims.width} height={canvasDims.height} style={{ transform: `scale(${canvasScale})` }} />
+ <canvas ref={canvasBackgroundRef} width={canvasDims.width} height={canvasDims.height} style={{ transform: `scale(${canvasScale})` }} />
+ <div
+ className="pointer"
+ style={{
+ left: cursorData.x,
+ top: cursorData.y,
+ width: cursorData.width,
+ height: cursorData.width,
+ }}>
+ <div className="innerPointer"></div>
+ </div>
+ {/* Icons */}
+ <div className="iconContainer">
+ {/* Undo and Redo */}
+ <IconButton
+ style={{ cursor: 'pointer' }}
+ onPointerDown={e => {
+ e.stopPropagation();
+ handleUndo();
+ }}
+ onPointerUp={e => {
+ e.stopPropagation();
+ }}
+ color={activeColor}
+ tooltip="Undo"
+ icon={<IoMdUndo />}
+ />
+ <IconButton
+ style={{ cursor: 'pointer' }}
+ onPointerDown={e => {
+ e.stopPropagation();
+ handleRedo();
+ }}
+ onPointerUp={e => {
+ e.stopPropagation();
+ }}
+ color={activeColor}
+ tooltip="Redo"
+ icon={<IoMdRedo />}
+ />
+ <div onPointerDown={e => e.stopPropagation()} style={{ height: 225, width: '100%', display: 'flex', justifyContent: 'center', cursor: 'pointer' }}>
+ <Slider
+ sx={{
+ '& input[type="range"]': {
+ WebkitAppearance: 'slider-vertical',
+ },
+ }}
+ orientation="vertical"
+ min={25}
+ max={500}
+ defaultValue={150}
+ size="small"
+ valueLabelDisplay="auto"
+ onChange={(e, val) => {
+ setCursorData(prev => ({ ...prev, width: val as number }));
+ }}
+ />
+ </div>
+ </div>
+ {/* Edits thumbnails*/}
+ <div className="editsBox">
+ {edits.map((edit, i) => (
+ <img
+ key={i}
+ width={75}
+ src={edit[0] as string}
+ style={{ cursor: 'pointer' }}
+ onClick={async () => {
+ const img = new Image();
+ img.src = edit[0] as string;
+ ImageUtility.drawImgToCanvas(img, canvasRef, canvasDims.width, canvasDims.height);
+ currImg.current = img;
+ parentDoc.current = edit[1] as Doc;
+ }}
+ />
+ ))}
+ {/* Original img thumbnail */}
+ {edits.length > 0 && (
+ <div style={{ position: 'relative' }}>
+ <label
+ style={{
+ position: 'absolute',
+ bottom: 10,
+ left: 10,
+ color: '#ffffff',
+ fontSize: '0.8rem',
+ letterSpacing: '1px',
+ textTransform: 'uppercase',
+ }}>
+ Original
+ </label>
+ <img
+ width={75}
+ src={originalImg.current?.src}
+ style={{ cursor: 'pointer' }}
+ onClick={() => {
+ if (!originalImg.current) return;
+ const img = new Image();
+ img.src = originalImg.current.src;
+ ImageUtility.drawImgToCanvas(img, canvasRef, canvasDims.width, canvasDims.height);
+ currImg.current = img;
+ parentDoc.current = originalDoc.current;
+ }}
+ />
+ </div>
+ )}
+ </div>
+ </div>
+ <div>
+ <TextField
+ value={input}
+ onChange={e => setInput(e.target.value)}
+ disabled={isBrushing}
+ type="text"
+ label="Prompt"
+ placeholder="Prompt..."
+ InputLabelProps={{ style: { fontSize: '16px' } }}
+ inputProps={{ style: { fontSize: '16px' } }}
+ sx={{
+ backgroundColor: '#ffffff',
+ position: 'absolute',
+ bottom: '16px',
+ transform: 'translateX(calc(50vw - 50%))',
+ width: 'calc(100vw - 64px)',
+ }}
+ />
+ </div>
+ </div>
+ );
+};
+
+export default GenerativeFill;
diff --git a/src/client/views/nodes/generativeFill/GenerativeFillButtons.scss b/src/client/views/nodes/generativeFill/GenerativeFillButtons.scss
new file mode 100644
index 000000000..0180ef904
--- /dev/null
+++ b/src/client/views/nodes/generativeFill/GenerativeFillButtons.scss
@@ -0,0 +1,4 @@
+.generativeFillBtnContainer {
+ display: flex;
+ gap: 1rem;
+}
diff --git a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
new file mode 100644
index 000000000..0dfcebea3
--- /dev/null
+++ b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
@@ -0,0 +1,42 @@
+import './GenerativeFillButtons.scss';
+import React = require('react');
+import ReactLoading from 'react-loading';
+import { activeColor } from './generativeFillUtils/generativeFillConstants';
+import { Button, Type } from 'browndash-components';
+
+interface ButtonContainerProps {
+ getEdit: () => Promise<void>;
+ loading: boolean;
+ onReset: () => void;
+}
+
+const Buttons = ({ loading, getEdit, onReset }: ButtonContainerProps) => {
+ return (
+ <div className="generativeFillBtnContainer">
+ <Button text="RESET" type={Type.PRIM} color={activeColor} onClick={onReset} />
+ {loading ? (
+ <Button
+ text="GET EDITS"
+ type={Type.TERT}
+ color={activeColor}
+ icon={<ReactLoading type="spin" color={'#ffffff'} width={20} height={20} />}
+ iconPlacement="right"
+ onClick={() => {
+ if (!loading) getEdit();
+ }}
+ />
+ ) : (
+ <Button
+ text="GET EDITS"
+ type={Type.TERT}
+ color={activeColor}
+ onClick={() => {
+ if (!loading) getEdit();
+ }}
+ />
+ )}
+ </div>
+ );
+};
+
+export default Buttons;
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts
new file mode 100644
index 000000000..f4ec70fbc
--- /dev/null
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts
@@ -0,0 +1,25 @@
+import { GenerativeFillMathHelpers } from './GenerativeFillMathHelpers';
+import { eraserColor } from './generativeFillConstants';
+import { Point } from './generativeFillInterfaces';
+
+export class BrushHandler {
+ static brushCircleOverlay = (x: number, y: number, brushRadius: number, ctx: CanvasRenderingContext2D, fillColor: string, erase: boolean) => {
+ ctx.globalCompositeOperation = 'destination-out';
+ ctx.fillStyle = fillColor;
+ ctx.shadowColor = eraserColor;
+ ctx.shadowBlur = 5;
+ ctx.beginPath();
+ ctx.arc(x, y, brushRadius, 0, 2 * Math.PI);
+ ctx.fill();
+ ctx.closePath();
+ };
+
+ static createBrushPathOverlay = (startPoint: Point, endPoint: Point, brushRadius: number, ctx: CanvasRenderingContext2D, fillColor: string, erase: boolean) => {
+ const dist = GenerativeFillMathHelpers.distanceBetween(startPoint, endPoint);
+
+ for (let i = 0; i < dist; i += 5) {
+ const s = i / dist;
+ BrushHandler.brushCircleOverlay(startPoint.x * (1 - s) + endPoint.x * s, startPoint.y * (1 - s) + endPoint.y * s, brushRadius, ctx, fillColor, erase);
+ }
+ };
+}
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts
new file mode 100644
index 000000000..97e03ff20
--- /dev/null
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts
@@ -0,0 +1,10 @@
+import { Point } from './generativeFillInterfaces';
+
+export class GenerativeFillMathHelpers {
+ static distanceBetween = (p1: Point, p2: Point) => {
+ return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
+ };
+ static angleBetween = (p1: Point, p2: Point) => {
+ return Math.atan2(p2.x - p1.x, p2.y - p1.y);
+ };
+}
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
new file mode 100644
index 000000000..2ede625f6
--- /dev/null
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
@@ -0,0 +1,286 @@
+import { RefObject } from 'react';
+import { bgColor, canvasSize } from './generativeFillConstants';
+
+export interface APISuccess {
+ status: 'success';
+ urls: string[];
+}
+
+export interface APIError {
+ status: 'error';
+ message: string;
+}
+
+export class ImageUtility {
+ /**
+ *
+ * @param canvas Canvas to convert
+ * @returns Blob of canvas
+ */
+ static canvasToBlob = (canvas: HTMLCanvasElement): Promise<Blob> => {
+ return new Promise(resolve => {
+ canvas.toBlob(blob => {
+ if (blob) {
+ resolve(blob);
+ }
+ }, 'image/png');
+ });
+ };
+
+ // given a square api image, get the cropped img
+ static getCroppedImg = (img: HTMLImageElement, width: number, height: number): HTMLCanvasElement | undefined => {
+ // Create a new canvas element
+ const canvas = document.createElement('canvas');
+ canvas.width = width;
+ canvas.height = height;
+ const ctx = canvas.getContext('2d');
+ if (ctx) {
+ // Clear the canvas
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ if (width < height) {
+ // horizontal padding, x offset
+ const xOffset = (canvasSize - width) / 2;
+ ctx.drawImage(img, xOffset, 0, canvas.width, canvas.height, 0, 0, canvas.width, canvas.height);
+ } else {
+ // vertical padding, y offset
+ const yOffset = (canvasSize - height) / 2;
+ ctx.drawImage(img, 0, yOffset, canvas.width, canvas.height, 0, 0, canvas.width, canvas.height);
+ }
+ return canvas;
+ }
+ };
+
+ // converts an image to a canvas data url
+ static convertImgToCanvasUrl = async (imageSrc: string, width: number, height: number): Promise<string> => {
+ return new Promise<string>((resolve, reject) => {
+ const img = new Image();
+ img.onload = () => {
+ const canvas = this.getCroppedImg(img, width, height);
+ if (canvas) {
+ const dataUrl = canvas.toDataURL();
+ resolve(dataUrl);
+ }
+ };
+ img.onerror = error => {
+ reject(error);
+ };
+ img.src = imageSrc;
+ });
+ };
+
+ // calls the openai api to get image edits
+ static getEdit = async (imgBlob: Blob, maskBlob: Blob, prompt: string, n?: number): Promise<APISuccess | APIError> => {
+ const apiUrl = 'https://api.openai.com/v1/images/edits';
+ const fd = new FormData();
+ fd.append('image', imgBlob, 'image.png');
+ fd.append('mask', maskBlob, 'mask.png');
+ fd.append('prompt', prompt);
+ fd.append('size', '1024x1024');
+ fd.append('n', n ? JSON.stringify(n) : '1');
+ fd.append('response_format', 'b64_json');
+
+ try {
+ const res = await fetch(apiUrl, {
+ method: 'POST',
+ headers: {
+ Authorization: `Bearer ${process.env.OPENAI_KEY}`,
+ },
+ body: fd,
+ });
+ const data = await res.json();
+ console.log(data.data);
+ return {
+ status: 'success',
+ urls: (data.data as { b64_json: string }[]).map(data => `data:image/png;base64,${data.b64_json}`),
+ };
+ } catch (err) {
+ console.log(err);
+ return { status: 'error', message: 'API error.' };
+ }
+ };
+
+ // mock api call
+ static mockGetEdit = async (mockSrc: string): Promise<APISuccess | APIError> => {
+ return {
+ status: 'success',
+ urls: [mockSrc, mockSrc, mockSrc],
+ };
+ };
+
+ // Gets the canvas rendering context of a canvas
+ static getCanvasContext = (canvasRef: RefObject<HTMLCanvasElement>): CanvasRenderingContext2D | null => {
+ if (!canvasRef.current) return null;
+ const ctx = canvasRef.current.getContext('2d');
+ if (!ctx) return null;
+ return ctx;
+ };
+
+ // Helper for downloading the canvas (for debugging)
+ static downloadCanvas = (canvas: HTMLCanvasElement) => {
+ const url = canvas.toDataURL();
+ const downloadLink = document.createElement('a');
+ downloadLink.href = url;
+ downloadLink.download = 'canvas';
+
+ downloadLink.click();
+ downloadLink.remove();
+ };
+
+ // Download the canvas (for debugging)
+ static downloadImageCanvas = (imgUrl: string) => {
+ const img = new Image();
+ img.src = imgUrl;
+ img.onload = () => {
+ const canvas = document.createElement('canvas');
+ canvas.width = canvasSize;
+ canvas.height = canvasSize;
+ const ctx = canvas.getContext('2d');
+ ctx?.drawImage(img, 0, 0, canvasSize, canvasSize);
+
+ this.downloadCanvas(canvas);
+ };
+ };
+
+ // Clears the canvas
+ static clearCanvas = (canvasRef: React.RefObject<HTMLCanvasElement>) => {
+ const ctx = this.getCanvasContext(canvasRef);
+ if (!ctx || !canvasRef.current) return;
+ ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
+ };
+
+ // Draws the image to the current canvas
+ static drawImgToCanvas = (img: HTMLImageElement, canvasRef: React.RefObject<HTMLCanvasElement>, width: number, height: number) => {
+ const drawImg = (img: HTMLImageElement) => {
+ const ctx = this.getCanvasContext(canvasRef);
+ if (!ctx) return;
+ ctx.globalCompositeOperation = 'source-over';
+ ctx.clearRect(0, 0, width, height);
+ ctx.drawImage(img, 0, 0, width, height);
+ };
+
+ if (img.complete) {
+ drawImg(img);
+ } else {
+ img.onload = () => {
+ drawImg(img);
+ };
+ }
+ };
+
+ // Gets the image mask for the openai endpoint
+ static getCanvasMask = (srcCanvas: HTMLCanvasElement, paddedCanvas: HTMLCanvasElement): HTMLCanvasElement | undefined => {
+ const canvas = document.createElement('canvas');
+ canvas.width = canvasSize;
+ canvas.height = canvasSize;
+ const ctx = canvas.getContext('2d');
+ if (!ctx) return;
+ ctx?.clearRect(0, 0, canvasSize, canvasSize);
+ ctx.drawImage(paddedCanvas, 0, 0);
+
+ // extract and set padding data
+ if (srcCanvas.height > srcCanvas.width) {
+ // horizontal padding, x offset
+ const xOffset = (canvasSize - srcCanvas.width) / 2;
+ ctx?.clearRect(xOffset, 0, srcCanvas.width, srcCanvas.height);
+ ctx.drawImage(srcCanvas, xOffset, 0, srcCanvas.width, srcCanvas.height);
+ } else {
+ // vertical padding, y offset
+ const yOffset = (canvasSize - srcCanvas.height) / 2;
+ ctx?.clearRect(0, yOffset, srcCanvas.width, srcCanvas.height);
+ ctx.drawImage(srcCanvas, 0, yOffset, srcCanvas.width, srcCanvas.height);
+ }
+ return canvas;
+ };
+
+ // Fills in the blank areas of the image with an image reflection (to fill in a square-shaped canvas)
+ static drawHorizontalReflection = (ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement, xOffset: number) => {
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
+ const data = imageData.data;
+ for (let i = 0; i < canvas.height; i++) {
+ for (let j = 0; j < xOffset; j++) {
+ const targetIdx = 4 * (i * canvas.width + j);
+ const sourceI = i;
+ const sourceJ = xOffset + (xOffset - j);
+ const sourceIdx = 4 * (sourceI * canvas.width + sourceJ);
+ data[targetIdx] = data[sourceIdx];
+ data[targetIdx + 1] = data[sourceIdx + 1];
+ data[targetIdx + 2] = data[sourceIdx + 2];
+ }
+ }
+ for (let i = 0; i < canvas.height; i++) {
+ for (let j = canvas.width - 1; j >= canvas.width - 1 - xOffset; j--) {
+ const targetIdx = 4 * (i * canvas.width + j);
+ const sourceI = i;
+ const sourceJ = canvas.width - 1 - xOffset - (xOffset - (canvas.width - j));
+ const sourceIdx = 4 * (sourceI * canvas.width + sourceJ);
+ data[targetIdx] = data[sourceIdx];
+ data[targetIdx + 1] = data[sourceIdx + 1];
+ data[targetIdx + 2] = data[sourceIdx + 2];
+ }
+ }
+ ctx.putImageData(imageData, 0, 0);
+ };
+
+ // Fills in the blank areas of the image with an image reflection (to fill in a square-shaped canvas)
+ static drawVerticalReflection = (ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement, yOffset: number) => {
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
+ const data = imageData.data;
+ for (let j = 0; j < canvas.width; j++) {
+ for (let i = 0; i < yOffset; i++) {
+ const targetIdx = 4 * (i * canvas.width + j);
+ const sourceJ = j;
+ const sourceI = yOffset + (yOffset - i);
+ const sourceIdx = 4 * (sourceI * canvas.width + sourceJ);
+ data[targetIdx] = data[sourceIdx];
+ data[targetIdx + 1] = data[sourceIdx + 1];
+ data[targetIdx + 2] = data[sourceIdx + 2];
+ }
+ }
+ for (let j = 0; j < canvas.width; j++) {
+ for (let i = canvas.height - 1; i >= canvas.height - 1 - yOffset; i--) {
+ const targetIdx = 4 * (i * canvas.width + j);
+ const sourceJ = j;
+ const sourceI = canvas.height - 1 - yOffset - (yOffset - (canvas.height - i));
+ const sourceIdx = 4 * (sourceI * canvas.width + sourceJ);
+ data[targetIdx] = data[sourceIdx];
+ data[targetIdx + 1] = data[sourceIdx + 1];
+ data[targetIdx + 2] = data[sourceIdx + 2];
+ }
+ }
+ ctx.putImageData(imageData, 0, 0);
+ };
+
+ // Gets the unaltered (besides filling in padding) version of the image for the api call
+ static getCanvasImg = (img: HTMLImageElement): HTMLCanvasElement | undefined => {
+ const canvas = document.createElement('canvas');
+ canvas.width = canvasSize;
+ canvas.height = canvasSize;
+ const ctx = canvas.getContext('2d');
+ if (!ctx) return;
+ // fix scaling
+ const scale = Math.min(canvasSize / img.width, canvasSize / img.height);
+ const width = Math.floor(img.width * scale);
+ const height = Math.floor(img.height * scale);
+ ctx?.clearRect(0, 0, canvasSize, canvasSize);
+ ctx.fillStyle = bgColor;
+ ctx.fillRect(0, 0, canvasSize, canvasSize);
+
+ // extract and set padding data
+ if (img.naturalHeight > img.naturalWidth) {
+ // horizontal padding, x offset
+ const xOffset = Math.floor((canvasSize - width) / 2);
+ ctx.drawImage(img, xOffset, 0, width, height);
+
+ // draw reflected image padding
+ this.drawHorizontalReflection(ctx, canvas, xOffset);
+ } else {
+ // vertical padding, y offset
+ const yOffset = Math.floor((canvasSize - height) / 2);
+ ctx.drawImage(img, 0, yOffset, width, height);
+
+ // draw reflected image padding
+ this.drawVerticalReflection(ctx, canvas, yOffset);
+ }
+ return canvas;
+ };
+}
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts
new file mode 100644
index 000000000..9e620ad11
--- /dev/null
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts
@@ -0,0 +1,15 @@
+import { Point } from "./generativeFillInterfaces";
+
+export class PointerHandler {
+ static getPointRelativeToElement = (
+ element: HTMLElement,
+ e: React.PointerEvent | PointerEvent,
+ scale: number
+ ): Point => {
+ const boundingBox = element.getBoundingClientRect();
+ return {
+ x: (e.clientX - boundingBox.x) / scale,
+ y: (e.clientY - boundingBox.y) / scale,
+ };
+ };
+}
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts
new file mode 100644
index 000000000..4772304bc
--- /dev/null
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts
@@ -0,0 +1,9 @@
+export const canvasSize = 1024;
+export const freeformRenderSize = 300;
+export const offsetDistanceY = freeformRenderSize + 400;
+export const offsetX = 200;
+export const newCollectionSize = 500;
+
+export const activeColor = '#1976d2';
+export const eraserColor = '#e1e9ec';
+export const bgColor = '#f0f4f6';
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillInterfaces.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillInterfaces.ts
new file mode 100644
index 000000000..1e7801056
--- /dev/null
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillInterfaces.ts
@@ -0,0 +1,20 @@
+export interface CursorData {
+ x: number;
+ y: number;
+ width: number;
+}
+
+export interface Point {
+ x: number;
+ y: number;
+}
+
+export enum BrushMode {
+ ADD,
+ SUBTRACT,
+}
+
+export interface ImageDimensions {
+ width: number;
+ height: number;
+}
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 8e53a87f6..b0924888a 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -49,9 +49,6 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
@observable public Status: 'marquee' | 'annotation' | '' = '';
// GPT additions
- @observable private GPTpopupText: string = '';
- @observable private loadingGPT: boolean = false;
- @observable private showGPTPopup: boolean = false;
@observable private GPTMode: GPTPopupMode = GPTPopupMode.SUMMARY;
@observable private selectedText: string = '';
@observable private editorView?: EditorView;
@@ -60,25 +57,11 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
private selectionRange: number[] | undefined;
@action
- setGPTPopupVis = (vis: boolean) => {
- this.showGPTPopup = vis;
- };
- @action
setGPTMode = (mode: GPTPopupMode) => {
this.GPTMode = mode;
};
@action
- setGPTPopupText = (txt: string) => {
- this.GPTpopupText = txt;
- };
-
- @action
- setLoading = (loading: boolean) => {
- this.loadingGPT = loading;
- };
-
- @action
setHighlightRange(r: number[] | undefined) {
this.highlightRange = r;
}
@@ -131,19 +114,12 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
componentDidMount() {
this._disposer2 = reaction(
() => this._opacity,
- opacity => {
- if (!opacity) {
- this.setGPTPopupVis(false);
- this.setGPTPopupText('');
- }
- },
+ opacity => {},
{ fireImmediately: true }
);
this._disposer = reaction(
() => SelectionManager.Views().slice(),
selected => {
- this.setGPTPopupVis(false);
- this.setGPTPopupText('');
AnchorMenu.Instance.fadeOut(true);
}
);
@@ -154,67 +130,23 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
* @param e pointer down event
*/
gptSummarize = async (e: React.PointerEvent) => {
+ // move this logic to gptpopup, need to implement generate again
+ GPTPopup.Instance.setVisible(true);
this.setHighlightRange(undefined);
- this.setGPTPopupVis(true);
- this.setGPTMode(GPTPopupMode.SUMMARY);
- this.setLoading(true);
+ GPTPopup.Instance.setMode(GPTPopupMode.SUMMARY);
+ GPTPopup.Instance.setLoading(true);
try {
const res = await gptAPICall(this.selectedText, GPTCallType.SUMMARY);
if (res) {
- this.setGPTPopupText(res);
+ GPTPopup.Instance.setText(res);
} else {
- this.setGPTPopupText('Something went wrong.');
+ GPTPopup.Instance.setText('Something went wrong.');
}
} catch (err) {
console.error(err);
}
-
- this.setLoading(false);
- };
-
- /**
- * Makes a GPT call to edit selected text.
- * @returns nothing
- */
- gptEdit = async () => {
- if (!this.editorView) return;
- this.setHighlightRange(undefined);
- const state = this.editorView.state;
- const sel = state.selection;
- const fullText = state.doc.textBetween(0, this.editorView.state.doc.content.size, ' \n');
- const selectedText = state.doc.textBetween(sel.from, sel.to);
-
- this.setGPTPopupVis(true);
- this.setGPTMode(GPTPopupMode.EDIT);
- this.setLoading(true);
-
- try {
- let res = await gptAPICall(selectedText, GPTCallType.EDIT);
- // let res = await this.mockGPTCall();
- if (!res) return;
- res = res.trim();
- const resultText = fullText.slice(0, sel.from - 1) + res + fullText.slice(sel.to - 1);
-
- if (res) {
- this.setGPTPopupText(resultText);
- this.setHighlightRange([sel.from - 1, sel.from - 1 + res.length]);
- } else {
- this.setGPTPopupText('Something went wrong.');
- }
- } catch (err) {
- console.error(err);
- }
-
- this.setLoading(false);
- };
-
- /**
- * Replaces text suggestions from GPT.
- */
- replaceText = (replacement: string) => {
- if (!this.editorView || !this.textDoc) return;
- this.textDoc.text = replacement;
+ GPTPopup.Instance.setLoading(false);
};
pointerDown = (e: React.PointerEvent) => {
@@ -325,17 +257,6 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
color={StrCast(Doc.UserDoc().userColor)}
/>
)}
- <GPTPopup
- key="gptpopup"
- visible={this.showGPTPopup}
- text={this.GPTpopupText}
- highlightRange={this.highlightRange}
- loading={this.loadingGPT}
- callSummaryApi={this.gptSummarize}
- callEditApi={this.gptEdit}
- replaceText={this.replaceText}
- mode={this.GPTMode}
- />
{AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : (
<IconButton
tooltip="Click to Record Annotation" //
@@ -344,14 +265,6 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
color={StrCast(Doc.UserDoc().userColor)}
/>
)}
- {this.canEdit() && (
- <IconButton
- tooltip="AI edit suggestions" //
- onPointerDown={this.gptEdit}
- icon={<FontAwesomeIcon icon="pencil-alt" />}
- color={StrCast(Doc.UserDoc().userColor)}
- />
- )}
<Popup
tooltip="Find document to link to selected text" //
type={Type.PRIM}
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss
index 44413ede7..5d966395c 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.scss
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss
@@ -6,12 +6,6 @@ $button: #5b97ff;
$highlightedText: #82e0ff;
.summary-box {
- display: flex;
- flex-direction: column;
- justify-content: space-between;
- background-color: #ffffff;
- box-shadow: 0 2px 5px #7474748d;
- color: $textgrey;
position: fixed;
bottom: 10px;
right: 10px;
@@ -21,9 +15,16 @@ $highlightedText: #82e0ff;
padding: 15px;
padding-bottom: 0;
z-index: 999;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ background-color: #ffffff;
+ box-shadow: 0 2px 5px #7474748d;
+ color: $textgrey;
.summary-heading {
display: flex;
+ justify-content: space-between;
align-items: center;
border-bottom: 1px solid $greyborder;
padding-bottom: 5px;
@@ -110,6 +111,59 @@ $highlightedText: #82e0ff;
}
}
+.image-content-wrapper {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 8px;
+ padding-bottom: 16px;
+
+ .img-wrapper {
+ position: relative;
+ cursor: pointer;
+
+ .img-container {
+ pointer-events: none;
+ position: relative;
+
+ img {
+ pointer-events: all;
+ position: relative;
+ }
+ }
+
+ .img-container::after {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 0.5);
+ opacity: 0;
+ transition: opacity 0.3s ease;
+ }
+
+ .btn-container {
+ position: absolute;
+ right: 8px;
+ bottom: 8px;
+ opacity: 0;
+ transition: opacity 0.3s ease;
+ }
+
+ &:hover {
+ .img-container::after {
+ opacity: 1;
+ }
+
+ .btn-container {
+ opacity: 1;
+ }
+ }
+ }
+}
+
// Typist CSS
.Typist .Cursor {
display: inline-block;
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index 8bd060d4f..8bf626d73 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -1,82 +1,226 @@
import React = require('react');
+import './GPTPopup.scss';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, observable } from 'mobx';
import { observer } from 'mobx-react';
import ReactLoading from 'react-loading';
import Typist from 'react-typist';
import { Doc } from '../../../../fields/Doc';
-import { Docs } from '../../../documents/Documents';
-import './GPTPopup.scss';
+import { DocUtils, Docs } from '../../../documents/Documents';
+import { Button, IconButton, Type } from 'browndash-components';
+import { NumCast, StrCast } from '../../../../fields/Types';
+import { CgClose } from 'react-icons/cg';
+import { AnchorMenu } from '../AnchorMenu';
+import { gptImageCall } from '../../../apis/gpt/GPT';
+import { Networking } from '../../../Network';
+import { Utils } from '../../../../Utils';
export enum GPTPopupMode {
SUMMARY,
EDIT,
+ IMAGE,
}
-interface GPTPopupProps {
- visible: boolean;
- text: string;
- loading: boolean;
- mode: GPTPopupMode;
- callSummaryApi: (e: React.PointerEvent) => Promise<void>;
- callEditApi: (e: React.PointerEvent) => Promise<void>;
- replaceText: (replacement: string) => void;
- highlightRange?: number[];
-}
+interface GPTPopupProps {}
@observer
export class GPTPopup extends React.Component<GPTPopupProps> {
static Instance: GPTPopup;
@observable
- private done: boolean = false;
+ public visible: boolean = false;
+ @action
+ public setVisible = (vis: boolean) => {
+ this.visible = vis;
+ };
@observable
- private sidebarId: string = '';
+ public loading: boolean = false;
+ @action
+ public setLoading = (loading: boolean) => {
+ this.loading = loading;
+ };
+ @observable
+ public text: string = '';
+ @action
+ public setText = (text: string) => {
+ this.text = text;
+ };
+
+ @observable
+ public imgDesc: string = '';
+ @action
+ public setImgDesc = (text: string) => {
+ this.imgDesc = text;
+ };
+
+ @observable
+ public imgUrls: string[][] = [];
+ @action
+ public setImgUrls = (imgs: string[][]) => {
+ this.imgUrls = imgs;
+ };
+ @observable
+ public mode: GPTPopupMode = GPTPopupMode.SUMMARY;
+ @action
+ public setMode = (mode: GPTPopupMode) => {
+ this.mode = mode;
+ };
+
+ @observable
+ public highlightRange: number[] = [];
+ @action callSummaryApi = () => {};
+ @action callEditApi = () => {};
+ @action replaceText = (replacement: string) => {};
+
+ @observable
+ private done: boolean = false;
@action
public setDone = (done: boolean) => {
this.done = done;
};
+
+ // change what can be a ref into a ref
+ @observable
+ private sidebarId: string = '';
@action
public setSidebarId = (id: string) => {
this.sidebarId = id;
};
+
+ @observable
+ private imgTargetDoc: Doc | undefined;
+ @action
+ public setImgTargetDoc = (anchor: Doc) => {
+ this.imgTargetDoc = anchor;
+ };
+
+ @observable
+ private textAnchor: Doc | undefined;
+ @action
+ public setTextAnchor = (anchor: Doc) => {
+ this.textAnchor = anchor;
+ };
+
public addDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean = () => false;
+ public addToCollection: ((doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) | undefined;
+
+ /**
+ * Generates a Dalle image and uploads it to the server.
+ */
+ generateImage = async () => {
+ if (this.imgDesc === '') return;
+ this.setImgUrls([]);
+ this.setMode(GPTPopupMode.IMAGE);
+ this.setVisible(true);
+ this.setLoading(true);
+
+ try {
+ let image_urls = await gptImageCall(this.imgDesc);
+ if (image_urls && image_urls[0]) {
+ const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [image_urls[0]] });
+ const source = Utils.prepend(result.accessPaths.agnostic.client);
+ this.setImgUrls([[image_urls[0], source]]);
+ }
+ } catch (err) {
+ console.log(err);
+ return '';
+ }
+ GPTPopup.Instance.setLoading(false);
+ };
/**
* Transfers the summarization text to a sidebar annotation text document.
*/
private transferToText = () => {
- const newDoc = Docs.Create.TextDocument(this.props.text.trim(), {
+ const newDoc = Docs.Create.TextDocument(this.text.trim(), {
_width: 200,
_height: 50,
_layout_fitWidth: true,
_layout_autoHeight: true,
});
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
+ */
+ private transferToImage = (source: string) => {
+ const textAnchor = this.imgTargetDoc;
+ if (!textAnchor) return;
+ const newDoc = Docs.Create.ImageDocument(source, {
+ x: NumCast(textAnchor.x) + NumCast(textAnchor._width) + 10,
+ y: NumCast(textAnchor.y),
+ _height: 200,
+ _width: 200,
+ data_nativeWidth: 1024,
+ data_nativeHeight: 1024,
+ });
+ if (Doc.IsInMyOverlay(textAnchor)) {
+ newDoc.overlayX = textAnchor.x;
+ newDoc.overlayY = NumCast(textAnchor.y) + NumCast(textAnchor._height);
+ Doc.AddToMyOverlay(newDoc);
+ } else {
+ this.addToCollection?.(newDoc);
+ }
+ // Create link between prompt and image
+ DocUtils.MakeLink(textAnchor, newDoc, { link_relationship: 'Image Prompt' });
};
+ private getPreviewUrl = (source: string) => source.split('.').join('_m.');
+
constructor(props: GPTPopupProps) {
super(props);
GPTPopup.Instance = this;
}
componentDidUpdate = () => {
- if (this.props.loading) {
+ if (this.loading) {
this.setDone(false);
}
};
+ imageBox = () => {
+ return (
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
+ {this.heading('GENERATED IMAGE')}
+ <div className="image-content-wrapper">
+ {this.imgUrls.map(rawSrc => (
+ <div className="img-wrapper">
+ <div className="img-container">
+ <img key={rawSrc[0]} src={rawSrc[0]} width={150} height={150} alt="dalle generation" />
+ </div>
+ <div className="btn-container">
+ <Button text="Save Image" onClick={() => this.transferToImage(rawSrc[1])} color={StrCast(Doc.UserDoc().userColor)} type={Type.TERT} />
+ </div>
+ </div>
+ ))}
+ </div>
+ {!this.loading && (
+ <>
+ <IconButton tooltip="Generate Again" onClick={this.generateImage} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(Doc.UserDoc().userVariantColor)} />
+ </>
+ )}
+ </div>
+ );
+ };
+
summaryBox = () => (
<>
<div>
{this.heading('SUMMARY')}
<div className="content-wrapper">
- {!this.props.loading &&
+ {!this.loading &&
(!this.done ? (
<Typist
- key={this.props.text}
+ key={this.text}
avgTypingDelay={15}
cursor={{ hideWhenDone: true }}
onTypingDone={() => {
@@ -84,39 +228,32 @@ export class GPTPopup extends React.Component<GPTPopupProps> {
this.setDone(true);
}, 500);
}}>
- {this.props.text}
+ {this.text}
</Typist>
) : (
- this.props.text
+ this.text
))}
</div>
</div>
- {!this.props.loading && (
+ {!this.loading && (
<div className="btns-wrapper">
{this.done ? (
<>
- <button className="icon-btn" onPointerDown={e => this.props.callSummaryApi(e)}>
- <FontAwesomeIcon icon="redo-alt" size="lg" />
- </button>
- <button
- className="text-btn"
- onClick={e => {
- this.transferToText();
- }}>
- Transfer to Text
- </button>
+ <IconButton tooltip="Generate Again" onClick={this.callSummaryApi} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(Doc.UserDoc().userVariantColor)} />
+ <Button tooltip="Transfer to text" text="Transfer To Text" onClick={this.transferToText} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT} />
</>
) : (
<div className="summarizing">
<span>Summarizing</span>
<ReactLoading type="bubbles" color="#bcbcbc" width={20} height={20} />
- <button
- className="btn-secondary"
- onClick={e => {
+ <Button
+ text="Stop Animation"
+ onClick={() => {
this.setDone(true);
- }}>
- Stop Animation
- </button>
+ }}
+ color={StrCast(Doc.UserDoc().userVariantColor)}
+ type={Type.TERT}
+ />
</div>
)}
</div>
@@ -124,43 +261,6 @@ export class GPTPopup extends React.Component<GPTPopupProps> {
</>
);
- editBox = () => {
- const hr = this.props.highlightRange;
- return (
- <>
- <div>
- {this.heading('TEXT EDIT SUGGESTIONS')}
- <div className="content-wrapper">
- {hr && (
- <div>
- {this.props.text.slice(0, hr[0])} <span className="highlighted-text">{this.props.text.slice(hr[0], hr[1])}</span> {this.props.text.slice(hr[1])}
- </div>
- )}
- </div>
- </div>
- {hr && !this.props.loading && (
- <>
- <div className="btns-wrapper">
- <>
- <button className="icon-btn" onPointerDown={e => this.props.callEditApi(e)}>
- <FontAwesomeIcon icon="redo-alt" size="lg" />
- </button>
- <button
- className="text-btn"
- onClick={e => {
- this.props.replaceText(this.props.text);
- }}>
- Replace Text
- </button>
- </>
- </div>
- {this.aiWarning()}
- </>
- )}
- </>
- );
- };
-
aiWarning = () =>
this.done ? (
<div className="ai-warning">
@@ -174,14 +274,14 @@ export class GPTPopup extends React.Component<GPTPopupProps> {
heading = (headingText: string) => (
<div className="summary-heading">
<label className="summary-text">{headingText}</label>
- {this.props.loading && <ReactLoading type="spin" color="#bcbcbc" width={14} height={14} />}
+ {this.loading ? <ReactLoading type="spin" color="#bcbcbc" width={14} height={14} /> : <IconButton color={StrCast(Doc.UserDoc().userVariantColor)} tooltip="close" icon={<CgClose size="16px" />} onClick={() => this.setVisible(false)} />}
</div>
);
render() {
return (
- <div className="summary-box" style={{ display: this.props.visible ? 'flex' : 'none' }}>
- {this.props.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.editBox()}
+ <div className="summary-box" style={{ display: this.visible ? 'flex' : 'none' }}>
+ {this.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.mode === GPTPopupMode.IMAGE ? this.imageBox() : <></>}
</div>
);
}
diff --git a/src/fields/DocSymbols.ts b/src/fields/DocSymbols.ts
index 088903082..856c377fa 100644
--- a/src/fields/DocSymbols.ts
+++ b/src/fields/DocSymbols.ts
@@ -24,4 +24,4 @@ export const Initializing = Symbol('DocInitializing');
export const ForceServerWrite = Symbol('DocForceServerWrite');
export const CachedUpdates = Symbol('DocCachedUpdates');
-export const DashVersion = 'v0.5.6';
+export const DashVersion = 'v0.5.7';
diff --git a/src/server/ApiManagers/AzureManager.ts b/src/server/ApiManagers/AzureManager.ts
index 12bb98ad0..2d0ab3aa6 100644
--- a/src/server/ApiManagers/AzureManager.ts
+++ b/src/server/ApiManagers/AzureManager.ts
@@ -1,8 +1,18 @@
import { ContainerClient, BlobServiceClient } from "@azure/storage-blob";
import * as fs from "fs";
import { Readable, Stream } from "stream";
+import * as path from "path";
const AZURE_STORAGE_CONNECTION_STRING = process.env.AZURE_STORAGE_CONNECTION_STRING;
+const extToType: { [suffix: string]: string } = {
+ ".jpeg" : "image/jpeg",
+ ".jpg" : "image/jpeg",
+ ".png" : "image/png",
+ ".svg" : "image/svg+xml",
+ ".webp" : "image/webp",
+ ".gif" : "image/gif"
+}
+
export class AzureManager {
private _containerClient: ContainerClient;
private _blobServiceClient: BlobServiceClient;
@@ -10,6 +20,7 @@ export class AzureManager {
public static CONTAINER_NAME = "dashmedia";
public static STORAGE_ACCOUNT_NAME = "dashblobstore";
+ public static BASE_STRING = `https://${AzureManager.STORAGE_ACCOUNT_NAME}.blob.core.windows.net/${AzureManager.CONTAINER_NAME}`;
constructor() {
if (!AZURE_STORAGE_CONNECTION_STRING) {
@@ -38,6 +49,14 @@ export class AzureManager {
return blockBlobClient.uploadStream(stream, undefined, undefined, blobOptions);
}
+ public static UploadBase64ImageBlob(filename: string, data: string, filetype?: string) {
+ const confirmedFiletype = filetype ? filetype : extToType[path.extname(filename)];
+ const buffer = Buffer.from(data, "base64");
+ const blockBlobClient = this.Instance.ContainerClient.getBlockBlobClient(filename);
+ const blobOptions = { blobHTTPHeaders: { blobContentType: confirmedFiletype } };
+ return blockBlobClient.upload(buffer, buffer.length, blobOptions);
+ }
+
public static UploadBlobStream(stream: Readable, filename: string, filetype: string) {
const blockBlobClient = this.Instance.ContainerClient.getBlockBlobClient(filename);
const blobOptions = { blobHTTPHeaders: { blobContentType: filetype }};
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index 117981a7c..e5e15ce99 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -383,13 +383,18 @@ export namespace DashUploadUtils {
if ((rawMatches = /^data:image\/([a-z]+);base64,(.*)/.exec(source)) !== null) {
const [ext, data] = rawMatches.slice(1, 3);
const resolved = (filename = `upload_${Utils.GenerateGuid()}.${ext}`);
- const error = await new Promise<Error | null>(resolve => {
- writeFile(serverPathToFile(Directory.images, resolved), data, 'base64', resolve);
- });
- if (error !== null) {
- return error;
+ if (usingAzure()) {
+ const response = await AzureManager.UploadBase64ImageBlob(resolved, data);
+ source = `${AzureManager.BASE_STRING}/${resolved}`;
+ } else {
+ const error = await new Promise<Error | null>(resolve => {
+ writeFile(serverPathToFile(Directory.images, resolved), data, 'base64', resolve);
+ });
+ if (error !== null) {
+ return error;
+ }
+ source = `${resolvedServerUrl}${clientPathToFile(Directory.images, resolved)}`;
}
- source = `${resolvedServerUrl}${clientPathToFile(Directory.images, resolved)}`;
}
let resolvedUrl: string;
/**