aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgeireann <60007097+geireann@users.noreply.github.com>2020-07-05 12:32:20 +0800
committergeireann <60007097+geireann@users.noreply.github.com>2020-07-05 12:32:20 +0800
commitc7dd9ba9e953adfb8c117bee3b95528d8d8c7304 (patch)
tree6aebd235aadb8ff4a4a464697ec4eb9fdcbb091a
parent2c04b0ae3689d931675151acbec24c88ea00daa1 (diff)
parent641ddbeefbbc05f8bf586c542595af672826149b (diff)
Merge branch 'master' into presentation_updates
-rw-r--r--deploy/mobile/image.html5
-rw-r--r--package-lock.json467
-rw-r--r--package.json5
-rw-r--r--src/Utils.ts16
-rw-r--r--src/client/documents/Documents.ts38
-rw-r--r--src/client/util/CurrentUserUtils.ts164
-rw-r--r--src/client/util/DocumentManager.ts8
-rw-r--r--src/client/util/InteractionUtils.tsx8
-rw-r--r--src/client/util/LinkManager.ts2
-rw-r--r--src/client/util/SettingsManager.scss21
-rw-r--r--src/client/views/AntimodeMenu.scss31
-rw-r--r--src/client/views/AntimodeMenu.tsx11
-rw-r--r--src/client/views/ContextMenu.tsx2
-rw-r--r--src/client/views/DocumentButtonBar.tsx4
-rw-r--r--src/client/views/DocumentDecorations.tsx92
-rw-r--r--src/client/views/EditableView.tsx1
-rw-r--r--src/client/views/GestureOverlay.scss2
-rw-r--r--src/client/views/GestureOverlay.tsx21
-rw-r--r--src/client/views/GlobalKeyHandler.ts16
-rw-r--r--src/client/views/InkingStroke.tsx4
-rw-r--r--src/client/views/MainView.tsx15
-rw-r--r--src/client/views/PreviewCursor.tsx21
-rw-r--r--src/client/views/collections/CollectionCarouselView.tsx2
-rw-r--r--src/client/views/collections/CollectionLinearView.scss64
-rw-r--r--src/client/views/collections/CollectionLinearView.tsx34
-rw-r--r--src/client/views/collections/CollectionSchemaCells.tsx536
-rw-r--r--src/client/views/collections/CollectionSchemaHeaders.tsx67
-rw-r--r--src/client/views/collections/CollectionSchemaMovableTableHOC.tsx2
-rw-r--r--src/client/views/collections/CollectionSchemaView.scss105
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx985
-rw-r--r--src/client/views/collections/CollectionStackingView.scss14
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx2
-rw-r--r--src/client/views/collections/CollectionSubView.tsx62
-rw-r--r--src/client/views/collections/CollectionView.tsx52
-rw-r--r--src/client/views/collections/CollectionViewChromes.scss7
-rw-r--r--src/client/views/collections/CollectionViewChromes.tsx10
-rw-r--r--src/client/views/collections/SchemaTable.tsx614
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx41
-rw-r--r--src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss39
-rw-r--r--src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx6
-rw-r--r--src/client/views/linking/LinkMenu.scss9
-rw-r--r--src/client/views/linking/LinkMenu.tsx13
-rw-r--r--src/client/views/linking/LinkMenuGroup.tsx4
-rw-r--r--src/client/views/linking/LinkMenuItem.scss11
-rw-r--r--src/client/views/linking/LinkMenuItem.tsx47
-rw-r--r--src/client/views/nodes/AudioBox.scss153
-rw-r--r--src/client/views/nodes/AudioBox.tsx6
-rw-r--r--src/client/views/nodes/ColorBox.tsx2
-rw-r--r--src/client/views/nodes/ComparisonBox.scss2
-rw-r--r--src/client/views/nodes/DocumentLinksButton.scss2
-rw-r--r--src/client/views/nodes/DocumentLinksButton.tsx86
-rw-r--r--src/client/views/nodes/DocumentView.tsx59
-rw-r--r--src/client/views/nodes/FontIconBox.tsx9
-rw-r--r--src/client/views/nodes/ImageBox.scss2
-rw-r--r--src/client/views/nodes/ImageBox.tsx11
-rw-r--r--src/client/views/nodes/LinkAnchorBox.scss7
-rw-r--r--src/client/views/nodes/LinkCreatedBox.scss21
-rw-r--r--src/client/views/nodes/LinkCreatedBox.tsx31
-rw-r--r--src/client/views/nodes/LinkDocPreview.tsx56
-rw-r--r--src/client/views/nodes/PDFBox.scss325
-rw-r--r--src/client/views/nodes/PDFBox.tsx9
-rw-r--r--src/client/views/nodes/PresBox.scss49
-rw-r--r--src/client/views/nodes/RadialMenu.tsx6
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.scss320
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx192
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBoxComment.scss86
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx158
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts24
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.scss6
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx224
-rw-r--r--src/client/views/nodes/formattedText/RichTextRules.ts5
-rw-r--r--src/client/views/nodes/formattedText/marks_rts.ts20
-rw-r--r--src/client/views/nodes/formattedText/nodes_rts.ts6
-rw-r--r--src/client/views/nodes/formattedText/prosemirrorPatches.js55
-rw-r--r--src/client/views/pdf/PDFMenu.scss19
-rw-r--r--src/client/views/pdf/PDFMenu.tsx68
-rw-r--r--src/client/views/pdf/PDFViewer.tsx38
-rw-r--r--src/client/views/presentationview/PresElementBox.scss2
-rw-r--r--src/client/views/presentationview/PresElementBox.tsx2
-rw-r--r--src/fields/DateField.ts4
-rw-r--r--src/fields/Doc.ts4
-rw-r--r--src/fields/RichTextUtils.ts2
-rw-r--r--src/fields/util.ts2
-rw-r--r--src/mobile/AudioUpload.scss61
-rw-r--r--src/mobile/AudioUpload.tsx152
-rw-r--r--src/mobile/ImageUpload.scss130
-rw-r--r--src/mobile/ImageUpload.tsx227
-rw-r--r--src/mobile/MobileInkOverlay.tsx4
-rw-r--r--src/mobile/MobileInterface.scss444
-rw-r--r--src/mobile/MobileInterface.tsx871
-rw-r--r--src/mobile/MobileMain.tsx25
-rw-r--r--src/mobile/MobileMenu.scss0
-rw-r--r--src/pen-gestures/ndollar.ts2
-rw-r--r--src/server/ApiManagers/PDFManager.ts2
-rw-r--r--src/server/index.ts1
-rw-r--r--webpack.config.js64
96 files changed, 5936 insertions, 1800 deletions
diff --git a/deploy/mobile/image.html b/deploy/mobile/image.html
index 6424d2a60..d30ad6ac2 100644
--- a/deploy/mobile/image.html
+++ b/deploy/mobile/image.html
@@ -1,15 +1,14 @@
<html>
<head>
- <title>Test view</title>
+ <title>Dash Mobile</title>
<link href="https://fonts.googleapis.com/css?family=Fjalla+One|Hind+Siliguri:300" rel="stylesheet">
</head>
<body>
<div id="root">
- <p>Capture Image: <input type="file" accept="image/*" id="capture">
</div>
- <script src="../imageUpload.js"></script>
+ <script src="../mobileInterface.js"></script>
</body>
</html> \ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 957641213..ad181758c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -372,6 +372,101 @@
"resolved": "https://registry.npmjs.org/@log4js-node/log4js-api/-/log4js-api-1.0.2.tgz",
"integrity": "sha512-6SJfx949YEWooh/CUPpJ+F491y4BYJmknz4hUN1+RHvKoUEynKbRmhnwbk/VLmh4OthLLDNCyWXfbh4DG1cTXA=="
},
+ "@material-ui/core": {
+ "version": "4.11.0",
+ "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.11.0.tgz",
+ "integrity": "sha512-bYo9uIub8wGhZySHqLQ833zi4ZML+XCBE1XwJ8EuUVSpTWWG57Pm+YugQToJNFsEyiKFhPh8DPD0bgupz8n01g==",
+ "requires": {
+ "@babel/runtime": "^7.4.4",
+ "@material-ui/styles": "^4.10.0",
+ "@material-ui/system": "^4.9.14",
+ "@material-ui/types": "^5.1.0",
+ "@material-ui/utils": "^4.10.2",
+ "@types/react-transition-group": "^4.2.0",
+ "clsx": "^1.0.4",
+ "hoist-non-react-statics": "^3.3.2",
+ "popper.js": "1.16.1-lts",
+ "prop-types": "^15.7.2",
+ "react-is": "^16.8.0",
+ "react-transition-group": "^4.4.0"
+ },
+ "dependencies": {
+ "dom-helpers": {
+ "version": "5.1.4",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.1.4.tgz",
+ "integrity": "sha512-TjMyeVUvNEnOnhzs6uAn9Ya47GmMo3qq7m+Lr/3ON0Rs5kHvb8I+SQYjLUSYn7qhEm0QjW0yrBkvz9yOrwwz1A==",
+ "requires": {
+ "@babel/runtime": "^7.8.7",
+ "csstype": "^2.6.7"
+ }
+ },
+ "popper.js": {
+ "version": "1.16.1-lts",
+ "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1-lts.tgz",
+ "integrity": "sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA=="
+ },
+ "react-transition-group": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz",
+ "integrity": "sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==",
+ "requires": {
+ "@babel/runtime": "^7.5.5",
+ "dom-helpers": "^5.0.1",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.6.2"
+ }
+ }
+ }
+ },
+ "@material-ui/styles": {
+ "version": "4.10.0",
+ "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.10.0.tgz",
+ "integrity": "sha512-XPwiVTpd3rlnbfrgtEJ1eJJdFCXZkHxy8TrdieaTvwxNYj42VnnCyFzxYeNW9Lhj4V1oD8YtQ6S5Gie7bZDf7Q==",
+ "requires": {
+ "@babel/runtime": "^7.4.4",
+ "@emotion/hash": "^0.8.0",
+ "@material-ui/types": "^5.1.0",
+ "@material-ui/utils": "^4.9.6",
+ "clsx": "^1.0.4",
+ "csstype": "^2.5.2",
+ "hoist-non-react-statics": "^3.3.2",
+ "jss": "^10.0.3",
+ "jss-plugin-camel-case": "^10.0.3",
+ "jss-plugin-default-unit": "^10.0.3",
+ "jss-plugin-global": "^10.0.3",
+ "jss-plugin-nested": "^10.0.3",
+ "jss-plugin-props-sort": "^10.0.3",
+ "jss-plugin-rule-value-function": "^10.0.3",
+ "jss-plugin-vendor-prefixer": "^10.0.3",
+ "prop-types": "^15.7.2"
+ }
+ },
+ "@material-ui/system": {
+ "version": "4.9.14",
+ "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.9.14.tgz",
+ "integrity": "sha512-oQbaqfSnNlEkXEziDcJDDIy8pbvwUmZXWNqlmIwDqr/ZdCK8FuV3f4nxikUh7hvClKV2gnQ9djh5CZFTHkZj3w==",
+ "requires": {
+ "@babel/runtime": "^7.4.4",
+ "@material-ui/utils": "^4.9.6",
+ "csstype": "^2.5.2",
+ "prop-types": "^15.7.2"
+ }
+ },
+ "@material-ui/types": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/@material-ui/types/-/types-5.1.0.tgz",
+ "integrity": "sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A=="
+ },
+ "@material-ui/utils": {
+ "version": "4.10.2",
+ "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.10.2.tgz",
+ "integrity": "sha512-eg29v74P7W5r6a4tWWDAAfZldXIzfyO1am2fIsC39hdUUHm/33k6pGOKPbgDjg/U/4ifmgAePy/1OjkKN6rFRw==",
+ "requires": {
+ "@babel/runtime": "^7.4.4",
+ "prop-types": "^15.7.2",
+ "react-is": "^16.8.0"
+ }
+ },
"@rkusa/linebreak": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@rkusa/linebreak/-/linebreak-1.0.0.tgz",
@@ -853,6 +948,16 @@
"@types/prosemirror-view": "*"
}
},
+ "@types/prosemirror-dev-tools": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@types/prosemirror-dev-tools/-/prosemirror-dev-tools-2.1.0.tgz",
+ "integrity": "sha512-OhnSaC4yrrEMLPRUkEWcHAIPVqgKlLkE4kISqL3cHeAYxASouSPvPMLqhBIbWkGwaozy43DjjVC1OXkxTo+y5Q==",
+ "dev": true,
+ "requires": {
+ "@types/prosemirror-state": "*",
+ "@types/prosemirror-view": "*"
+ }
+ },
"@types/prosemirror-history": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/prosemirror-history/-/prosemirror-history-1.0.1.tgz",
@@ -1004,6 +1109,17 @@
"@types/reactcss": "*"
}
},
+ "@types/react-datepicker": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/react-datepicker/-/react-datepicker-3.0.2.tgz",
+ "integrity": "sha512-xW04NZRF+9ZnzOD3XrlIzBEKgUsN6LVgZJJsXH8NIUlVjyPh+sdtLPfVoDp+GQzGq1M0TuMLNZsv0sJ3N9XwDA==",
+ "dev": true,
+ "requires": {
+ "@types/react": "*",
+ "date-fns": "^2.0.1",
+ "popper.js": "^1.14.1"
+ }
+ },
"@types/react-dom": {
"version": "16.9.8",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.8.tgz",
@@ -1054,7 +1170,6 @@
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.0.tgz",
"integrity": "sha512-/QfLHGpu+2fQOqQaXh8MG9q03bFENooTb/it4jr5kKaZlDQfWvjqWZg48AwzPVMBHlRuTRAY7hRHCEOXz5kV6w==",
- "dev": true,
"requires": {
"@types/react": "*"
}
@@ -2457,6 +2572,11 @@
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.5.0.tgz",
"integrity": "sha512-Z93QoXvodoVslA+PWNdk23Hze4RBYIkpb5h8I2HY2Tu2h7A0LpAgLcyrhrSUyo2/Oxm2l1fRZPs1e5hnxnliXA=="
},
+ "bowser": {
+ "version": "1.9.4",
+ "resolved": "https://registry.npmjs.org/bowser/-/bowser-1.9.4.tgz",
+ "integrity": "sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ=="
+ },
"boxen": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz",
@@ -2857,6 +2977,11 @@
"type-detect": "^4.0.5"
}
},
+ "chain-function": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/chain-function/-/chain-function-1.0.1.tgz",
+ "integrity": "sha512-SxltgMwL9uCko5/ZCLiyG2B7R9fY4pDZUw7hJ4MhirdjBLosoDqkWABi3XMucddHdLiFJMb7PD2MZifZriuMTg=="
+ },
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
@@ -2867,6 +2992,11 @@
"supports-color": "^5.3.0"
}
},
+ "change-emitter": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/change-emitter/-/change-emitter-0.1.6.tgz",
+ "integrity": "sha1-6LL+PX8at9aaMhma/5HqaTFAlRU="
+ },
"character-parser": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz",
@@ -2955,7 +3085,8 @@
},
"ansi-regex": {
"version": "2.1.1",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"aproba": {
"version": "1.2.0",
@@ -2973,11 +3104,13 @@
},
"balanced-match": {
"version": "1.0.0",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
+ "optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -2990,15 +3123,18 @@
},
"code-point-at": {
"version": "1.1.0",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"concat-map": {
"version": "0.0.1",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"console-control-strings": {
"version": "1.1.0",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"core-util-is": {
"version": "1.0.2",
@@ -3101,7 +3237,8 @@
},
"inherits": {
"version": "2.0.4",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"ini": {
"version": "1.3.5",
@@ -3111,6 +3248,7 @@
"is-fullwidth-code-point": {
"version": "1.0.0",
"bundled": true,
+ "optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -3123,17 +3261,20 @@
"minimatch": {
"version": "3.0.4",
"bundled": true,
+ "optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "1.2.5",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"minipass": {
"version": "2.9.0",
"bundled": true,
+ "optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -3150,6 +3291,7 @@
"mkdirp": {
"version": "0.5.3",
"bundled": true,
+ "optional": true,
"requires": {
"minimist": "^1.2.5"
}
@@ -3205,7 +3347,8 @@
},
"npm-normalize-package-bin": {
"version": "1.0.1",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"npm-packlist": {
"version": "1.4.8",
@@ -3230,7 +3373,8 @@
},
"number-is-nan": {
"version": "1.0.1",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"object-assign": {
"version": "4.1.1",
@@ -3240,6 +3384,7 @@
"once": {
"version": "1.4.0",
"bundled": true,
+ "optional": true,
"requires": {
"wrappy": "1"
}
@@ -3308,7 +3453,8 @@
},
"safe-buffer": {
"version": "5.1.2",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -3338,6 +3484,7 @@
"string-width": {
"version": "1.0.2",
"bundled": true,
+ "optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -3355,6 +3502,7 @@
"strip-ansi": {
"version": "3.0.1",
"bundled": true,
+ "optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -3393,11 +3541,13 @@
},
"wrappy": {
"version": "1.0.2",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"yallist": {
"version": "3.1.1",
- "bundled": true
+ "bundled": true,
+ "optional": true
}
}
}
@@ -3573,6 +3723,11 @@
"shallow-clone": "^3.0.0"
}
},
+ "clsx": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz",
+ "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA=="
+ },
"code-point-at": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
@@ -4222,6 +4377,15 @@
"resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz",
"integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4="
},
+ "css-in-js-utils": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-2.0.1.tgz",
+ "integrity": "sha512-PJF0SpJT+WdbVVt0AOYp9C8GnuruRlL/UFW7932nLWmFLQTaWEzTBQEx7/hn4BuV+WON75iAViSUJLiU3PKbpA==",
+ "requires": {
+ "hyphenate-style-name": "^1.0.2",
+ "isobject": "^3.0.1"
+ }
+ },
"css-loader": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-2.1.1.tgz",
@@ -4276,6 +4440,15 @@
}
}
},
+ "css-vendor": {
+ "version": "2.0.8",
+ "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz",
+ "integrity": "sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==",
+ "requires": {
+ "@babel/runtime": "^7.8.3",
+ "is-in-browser": "^1.0.2"
+ }
+ },
"css-what": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz",
@@ -4368,6 +4541,11 @@
"whatwg-url": "^7.0.0"
}
},
+ "date-fns": {
+ "version": "2.14.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.14.0.tgz",
+ "integrity": "sha512-1zD+68jhFgDIM0rF05rcwYO8cExdNqxjq4xP1QKM60Q45mnO6zaMWB4tOzrIr4M4GSLntsKeE4c9Bdl2jhL/yw=="
+ },
"dateformat": {
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz",
@@ -4429,7 +4607,6 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz",
"integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==",
- "dev": true,
"requires": {
"is-arguments": "^1.0.4",
"is-date-object": "^1.0.1",
@@ -6987,6 +7164,11 @@
}
}
},
+ "hyphenate-style-name": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz",
+ "integrity": "sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ=="
+ },
"i": {
"version": "0.3.6",
"resolved": "https://registry.npmjs.org/i/-/i-0.3.6.tgz",
@@ -7202,6 +7384,15 @@
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
},
+ "inline-style-prefixer": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-3.0.8.tgz",
+ "integrity": "sha1-hVG45bTVcyROZqNLBPfTIHaitTQ=",
+ "requires": {
+ "bowser": "^1.7.3",
+ "css-in-js-utils": "^2.0.0"
+ }
+ },
"inspect-function": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/inspect-function/-/inspect-function-0.2.2.tgz",
@@ -7380,8 +7571,7 @@
"is-arguments": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz",
- "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==",
- "dev": true
+ "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA=="
},
"is-arrayish": {
"version": "0.2.1",
@@ -7511,6 +7701,11 @@
"is-extglob": "^2.1.1"
}
},
+ "is-in-browser": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz",
+ "integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU="
+ },
"is-installed-globally": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz",
@@ -7878,6 +8073,84 @@
"verror": "1.10.0"
}
},
+ "jss": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/jss/-/jss-10.3.0.tgz",
+ "integrity": "sha512-B5sTRW9B6uHaUVzSo9YiMEOEp3UX8lWevU0Fsv+xtRnsShmgCfIYX44bTH8bPJe6LQKqEXku3ulKuHLbxBS97Q==",
+ "requires": {
+ "@babel/runtime": "^7.3.1",
+ "csstype": "^2.6.5",
+ "is-in-browser": "^1.1.3",
+ "tiny-warning": "^1.0.2"
+ }
+ },
+ "jss-plugin-camel-case": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.3.0.tgz",
+ "integrity": "sha512-tadWRi/SLWqLK3EUZEdDNJL71F3ST93Zrl9JYMjV0QDqKPAl0Liue81q7m/nFUpnSTXczbKDy4wq8rI8o7WFqA==",
+ "requires": {
+ "@babel/runtime": "^7.3.1",
+ "hyphenate-style-name": "^1.0.3",
+ "jss": "^10.3.0"
+ }
+ },
+ "jss-plugin-default-unit": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.3.0.tgz",
+ "integrity": "sha512-tT5KkIXAsZOSS9WDSe8m8lEHIjoEOj4Pr0WrG0WZZsMXZ1mVLFCSsD2jdWarQWDaRNyMj/I4d7czRRObhOxSuw==",
+ "requires": {
+ "@babel/runtime": "^7.3.1",
+ "jss": "^10.3.0"
+ }
+ },
+ "jss-plugin-global": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.3.0.tgz",
+ "integrity": "sha512-etYTG/y3qIR/vxZnKY+J3wXwObyBDNhBiB3l/EW9/pE3WHE//BZdK8LFvQcrCO48sZW1Z6paHo6klxUPP7WbzA==",
+ "requires": {
+ "@babel/runtime": "^7.3.1",
+ "jss": "^10.3.0"
+ }
+ },
+ "jss-plugin-nested": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.3.0.tgz",
+ "integrity": "sha512-qWiEkoXNEkkZ+FZrWmUGpf+zBsnEOmKXhkjNX85/ZfWhH9dfGxUCKuJFuOWFM+rjQfxV4csfesq4hY0jk8Qt0w==",
+ "requires": {
+ "@babel/runtime": "^7.3.1",
+ "jss": "^10.3.0",
+ "tiny-warning": "^1.0.2"
+ }
+ },
+ "jss-plugin-props-sort": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.3.0.tgz",
+ "integrity": "sha512-boetORqL/lfd7BWeFD3K+IyPqyIC+l3CRrdZr+NPq7Noqp+xyg/0MR7QisgzpxCEulk+j2CRcEUoZsvgPC4nTg==",
+ "requires": {
+ "@babel/runtime": "^7.3.1",
+ "jss": "^10.3.0"
+ }
+ },
+ "jss-plugin-rule-value-function": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.3.0.tgz",
+ "integrity": "sha512-7WiMrKIHH3rwxTuJki9+7nY11r1UXqaUZRhHvqTD4/ZE+SVhvtD5Tx21ivNxotwUSleucA/8boX+NF21oXzr5Q==",
+ "requires": {
+ "@babel/runtime": "^7.3.1",
+ "jss": "^10.3.0",
+ "tiny-warning": "^1.0.2"
+ }
+ },
+ "jss-plugin-vendor-prefixer": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.3.0.tgz",
+ "integrity": "sha512-sZQbrcZyP5V0ADjCLwUA1spVWoaZvM7XZ+2fSeieZFBj31cRsnV7X70FFDerMHeiHAXKWzYek+67nMDjhrZAVQ==",
+ "requires": {
+ "@babel/runtime": "^7.3.1",
+ "css-vendor": "^2.0.8",
+ "jss": "^10.3.0"
+ }
+ },
"jstransformer": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz",
@@ -7911,6 +8184,11 @@
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.1.tgz",
"integrity": "sha512-l3hLhffs9zqoDe8zjmb/mAN4B8VT3L56EUvKNqLFVs9YlFA+zx7ke1DO8STAdDyYNkeSo1nKmjuvQeI12So8Xw=="
},
+ "keycode": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz",
+ "integrity": "sha1-PQr1bce4uOXLqNCpfxByBO7CKwQ="
+ },
"keygrip": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz",
@@ -8157,6 +8435,11 @@
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
"integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4="
},
+ "lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
+ },
"lodash.padend": {
"version": "4.6.1",
"resolved": "https://registry.npmjs.org/lodash.padend/-/lodash.padend-4.6.1.tgz",
@@ -8168,6 +8451,11 @@
"integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=",
"dev": true
},
+ "lodash.throttle": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
+ "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ="
+ },
"lodash.union": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz",
@@ -8327,6 +8615,38 @@
"resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz",
"integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg=="
},
+ "material-ui": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/material-ui/-/material-ui-0.20.2.tgz",
+ "integrity": "sha512-VeqgQkdvtK193w+FFvXDEwlVxI4rWk83eWbpYLeOIHDPWr3rbB9B075JRnJt/8IsI2X8q5Aia5W3+7m4KkleDg==",
+ "requires": {
+ "babel-runtime": "^6.23.0",
+ "inline-style-prefixer": "^3.0.8",
+ "keycode": "^2.1.8",
+ "lodash.merge": "^4.6.0",
+ "lodash.throttle": "^4.1.1",
+ "prop-types": "^15.5.7",
+ "react-event-listener": "^0.6.2",
+ "react-transition-group": "^1.2.1",
+ "recompose": "^0.26.0",
+ "simple-assign": "^0.1.0",
+ "warning": "^3.0.0"
+ },
+ "dependencies": {
+ "react-transition-group": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-1.2.1.tgz",
+ "integrity": "sha512-CWaL3laCmgAFdxdKbhhps+c0HRGF4c+hdM4H23+FI1QBNUyx/AMeIJGWorehPNSaKnQNOAxL7PQmqMu78CDj3Q==",
+ "requires": {
+ "chain-function": "^1.0.0",
+ "dom-helpers": "^3.2.0",
+ "loose-envify": "^1.3.1",
+ "prop-types": "^15.5.6",
+ "warning": "^3.0.0"
+ }
+ }
+ }
+ },
"md5.js": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
@@ -12447,7 +12767,6 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.2.tgz",
"integrity": "sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==",
- "dev": true,
"requires": {
"define-properties": "^1.1.3",
"es-abstract": "^1.17.5"
@@ -13062,6 +13381,11 @@
"integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==",
"dev": true
},
+ "popper.js": {
+ "version": "1.16.1",
+ "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
+ "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ=="
+ },
"portfinder": {
"version": "1.0.26",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.26.tgz",
@@ -13799,6 +14123,18 @@
"warning": "^3.0.0"
}
},
+ "react-datepicker": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-3.0.0.tgz",
+ "integrity": "sha512-Yrxan1tERAiWS0EzitpiaiXOIz0APTUtV75uWbaS+jSaKoGCR6wUN2FDwr1ACGlnEoGhR9QQ2Vq3odnWtgJsOA==",
+ "requires": {
+ "classnames": "^2.2.6",
+ "date-fns": "^2.0.1",
+ "prop-types": "^15.7.2",
+ "react-onclickoutside": "^6.9.0",
+ "react-popper": "^1.3.4"
+ }
+ },
"react-dock": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/react-dock/-/react-dock-0.2.4.tgz",
@@ -13828,6 +14164,26 @@
"prop-types": "^15.6.0"
}
},
+ "react-event-listener": {
+ "version": "0.6.6",
+ "resolved": "https://registry.npmjs.org/react-event-listener/-/react-event-listener-0.6.6.tgz",
+ "integrity": "sha512-+hCNqfy7o9wvO6UgjqFmBzARJS7qrNoda0VqzvOuioEpoEXKutiKuv92dSz6kP7rYLmyHPyYNLesi5t/aH1gfw==",
+ "requires": {
+ "@babel/runtime": "^7.2.0",
+ "prop-types": "^15.6.0",
+ "warning": "^4.0.1"
+ },
+ "dependencies": {
+ "warning": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
+ "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
+ "requires": {
+ "loose-envify": "^1.0.0"
+ }
+ }
+ }
+ },
"react-grid-layout": {
"version": "0.18.3",
"resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-0.18.3.tgz",
@@ -13932,6 +14288,44 @@
}
}
},
+ "react-onclickoutside": {
+ "version": "6.9.0",
+ "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.9.0.tgz",
+ "integrity": "sha512-8ltIY3bC7oGhj2nPAvWOGi+xGFybPNhJM0V1H8hY/whNcXgmDeaeoCMPPd8VatrpTsUWjb/vGzrmu6SrXVty3A=="
+ },
+ "react-popper": {
+ "version": "1.3.7",
+ "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.7.tgz",
+ "integrity": "sha512-nmqYTx7QVjCm3WUZLeuOomna138R1luC4EqkW3hxJUrAe+3eNz3oFCLYdnPwILfn0mX1Ew2c3wctrjlUMYYUww==",
+ "requires": {
+ "@babel/runtime": "^7.1.2",
+ "create-react-context": "^0.3.0",
+ "deep-equal": "^1.1.1",
+ "popper.js": "^1.14.4",
+ "prop-types": "^15.6.1",
+ "typed-styles": "^0.0.7",
+ "warning": "^4.0.2"
+ },
+ "dependencies": {
+ "create-react-context": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/create-react-context/-/create-react-context-0.3.0.tgz",
+ "integrity": "sha512-dNldIoSuNSvlTJ7slIKC/ZFGKexBMBrrcc+TTe1NdmROnaASuLPvqpwj9v4XS4uXZ8+YPu0sNmShX2rXI5LNsw==",
+ "requires": {
+ "gud": "^1.0.0",
+ "warning": "^4.0.3"
+ }
+ },
+ "warning": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
+ "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
+ "requires": {
+ "loose-envify": "^1.0.0"
+ }
+ }
+ }
+ },
"react-resizable": {
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-1.10.1.tgz",
@@ -14126,6 +14520,24 @@
"resolve": "^1.1.6"
}
},
+ "recompose": {
+ "version": "0.26.0",
+ "resolved": "https://registry.npmjs.org/recompose/-/recompose-0.26.0.tgz",
+ "integrity": "sha512-KwOu6ztO0mN5vy3+zDcc45lgnaUoaQse/a5yLVqtzTK13czSWnFGmXbQVmnoMgDkI5POd1EwIKSbjU1V7xdZog==",
+ "requires": {
+ "change-emitter": "^0.1.2",
+ "fbjs": "^0.8.1",
+ "hoist-non-react-statics": "^2.3.1",
+ "symbol-observable": "^1.0.4"
+ },
+ "dependencies": {
+ "hoist-non-react-statics": {
+ "version": "2.5.5",
+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
+ "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw=="
+ }
+ }
+ },
"redent": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
@@ -14163,7 +14575,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz",
"integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==",
- "dev": true,
"requires": {
"define-properties": "^1.1.3",
"es-abstract": "^1.17.0-next.1"
@@ -14891,6 +15302,11 @@
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
"integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA=="
},
+ "simple-assign": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/simple-assign/-/simple-assign-0.1.0.tgz",
+ "integrity": "sha1-F/0wZqXz13OPUDIbsPFMooHMS6o="
+ },
"simple-concat": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz",
@@ -15730,6 +16146,11 @@
"has-flag": "^3.0.0"
}
},
+ "symbol-observable": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
+ "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ=="
+ },
"symbol-tree": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
@@ -16025,6 +16446,11 @@
"resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
"integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="
},
+ "tiny-warning": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
+ "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
+ },
"tinycolor2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz",
@@ -16496,6 +16922,11 @@
"mime-types": "~2.1.24"
}
},
+ "typed-styles": {
+ "version": "0.0.7",
+ "resolved": "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.7.tgz",
+ "integrity": "sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q=="
+ },
"typedarray": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
diff --git a/package.json b/package.json
index 8f67704cb..75dc817f5 100644
--- a/package.json
+++ b/package.json
@@ -52,6 +52,7 @@
"@types/passport-local": "^1.0.33",
"@types/pdfjs-dist": "^2.1.4",
"@types/prosemirror-commands": "^1.0.1",
+ "@types/prosemirror-dev-tools": "^2.1.0",
"@types/prosemirror-history": "^1.0.1",
"@types/prosemirror-inputrules": "^1.0.2",
"@types/prosemirror-keymap": "^1.0.2",
@@ -66,6 +67,7 @@
"@types/react": "^16.9.41",
"@types/react-autosuggest": "^9.3.14",
"@types/react-color": "^2.17.4",
+ "@types/react-datepicker": "^3.0.2",
"@types/react-dom": "^16.9.8",
"@types/react-grid-layout": "^0.17.1",
"@types/react-measure": "^2.0.6",
@@ -119,6 +121,7 @@
"@hig/flyout": "^1.2.1",
"@hig/theme-context": "^2.1.3",
"@hig/theme-data": "^2.16.1",
+ "@material-ui/core": "^4.11.0",
"@types/google-maps": "^3.2.2",
"@types/webscopeio__react-textarea-autocomplete": "^4.6.1",
"@webscopeio/react-textarea-autocomplete": "^4.7.0",
@@ -170,6 +173,7 @@
"jsonschema": "^1.2.5",
"libxmljs": "^0.19.7",
"lodash": "^4.17.15",
+ "material-ui": "^0.20.2",
"mobile-detect": "^1.4.4",
"mobx": "^5.15.3",
"mobx-react": "^5.3.5",
@@ -211,6 +215,7 @@
"react-autosuggest": "^9.4.3",
"react-color": "^2.18.1",
"react-compound-slider": "^2.5.0",
+ "react-datepicker": "^3.0.0",
"react-dom": "^16.12.0",
"react-grid-layout": "^0.18.3",
"react-image-lightbox-with-rotate": "^5.1.1",
diff --git a/src/Utils.ts b/src/Utils.ts
index dba802f98..a01a94134 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -7,6 +7,22 @@ import { ColorState } from 'react-color';
export namespace Utils {
export let DRAG_THRESHOLD = 4;
+ export function readUploadedFileAsText(inputFile: File) {
+ const temporaryFileReader = new FileReader();
+
+ return new Promise((resolve, reject) => {
+ temporaryFileReader.onerror = () => {
+ temporaryFileReader.abort();
+ reject(new DOMException("Problem parsing input file."));
+ };
+
+ temporaryFileReader.onload = () => {
+ resolve(temporaryFileReader.result);
+ };
+ temporaryFileReader.readAsText(inputFile);
+ });
+ }
+
export function GenerateGuid(): string {
return v4();
}
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index b6c453847..0e44a46d9 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1,5 +1,5 @@
-import { runInAction } from "mobx";
-import { extname } from "path";
+import { runInAction, action } from "mobx";
+import { extname, basename } from "path";
import { DateField } from "../../fields/DateField";
import { Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym } from "../../fields/Doc";
import { HtmlField } from "../../fields/HtmlField";
@@ -48,6 +48,8 @@ import { PresElementBox } from "../views/presentationview/PresElementBox";
import { RecommendationsBox } from "../views/RecommendationsBox";
import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo";
import { DocumentType } from "./DocumentTypes";
+import { Networking } from "../Network";
+import { Upload } from "../../server/SharedMediaTypes";
const path = require('path');
export interface DocumentOptions {
@@ -415,7 +417,7 @@ export namespace Docs {
// synthesize the default options, the type and title from computed values and
// whatever options pertain to this specific prototype
const options = { title, type, baseProto: true, ...defaultOptions, ...(template.options || {}) };
- options.layout = layout.view.LayoutString(layout.dataField);
+ options.layout = layout.view?.LayoutString(layout.dataField);
const doc = Doc.assign(new Doc(prototypeId, true), { layoutKey: "layout", ...options });
doc.layout_keyValue = KeyValueBox.LayoutString("");
return doc;
@@ -865,6 +867,7 @@ export namespace DocUtils {
export function MakeLinkToActiveAudio(doc: Doc) {
DocUtils.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: doc }, { doc: d }, "audio link", "audio timeline"));
}
+
export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", id?: string) {
const sv = DocumentManager.Instance.getDocumentView(source.doc);
if (sv && sv.props.ContainingCollectionDoc === target.doc) return;
@@ -876,6 +879,7 @@ export namespace DocUtils {
Doc.GetProto(source.doc).links = ComputedField.MakeFunction("links(self)");
Doc.GetProto(target.doc).links = ComputedField.MakeFunction("links(self)");
+
return linkDoc;
}
@@ -978,7 +982,7 @@ export namespace DocUtils {
});
ContextMenu.Instance.addItem({
description: "Add Template Doc ...",
- subitems: DocListCast(Cast(Doc.UserDoc().dockedBtns, Doc, null)?.data).map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)).filter(doc => doc).map((dragDoc, i) => ({
+ subitems: DocListCast(Cast(Doc.UserDoc().myItemCreators, Doc, null)?.data).map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)).filter(doc => doc).map((dragDoc, i) => ({
description: ":" + StrCast(dragDoc.title),
event: (args: { x: number, y: number }) => {
const newDoc = Doc.ApplyTemplate(dragDoc);
@@ -1105,6 +1109,32 @@ export namespace DocUtils {
});
return optionsCollection;
}
+
+ export async function uploadFilesToDocs(files: File[], options: DocumentOptions) {
+ const generatedDocuments: Doc[] = [];
+ for (const { source: { name, type }, result } of await Networking.UploadFilesToServer(files)) {
+ if (result instanceof Error) {
+ alert(`Upload failed: ${result.message}`);
+ return [];
+ }
+ const full = { ...options, _width: 400, title: name };
+ const pathname = Utils.prepend(result.accessPaths.agnostic.client);
+ const doc = await DocUtils.DocumentFromType(type, pathname, full);
+ if (!doc) {
+ continue;
+ }
+ const proto = Doc.GetProto(doc);
+ proto.text = result.rawText;
+ proto.fileUpload = basename(pathname).replace("upload_", "").replace(/\.[a-z0-9]*$/, "");
+ if (Upload.isImageInformation(result)) {
+ proto["data-nativeWidth"] = (result.nativeWidth > result.nativeHeight) ? 400 * result.nativeWidth / result.nativeHeight : 400;
+ proto["data-nativeHeight"] = (result.nativeWidth > result.nativeHeight) ? 400 : 400 / (result.nativeWidth / result.nativeHeight);
+ proto.contentSize = result.contentSize;
+ }
+ generatedDocuments.push(doc);
+ }
+ return generatedDocuments;
+ }
}
Scripting.addGlobal("Docs", Docs);
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 09e4d2bb1..4276e04e4 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -54,6 +54,25 @@ export class CurrentUserUtils {
removeDropProperties: new List<string>(["dropAction"]), title: "query view", icon: "question-circle"
});
}
+ // Prototype for mobile button (not sure if 'Advanced Item Prototypes' is ideal location)
+ if (doc["template-mobile-button"] === undefined) {
+ const queryTemplate = this.mobileButton({
+ title: "NEW MOBILE BUTTON",
+ onClick: undefined,
+ },
+ [this.ficon({
+ ignoreClick: true,
+ icon: "mobile",
+ backgroundColor: "rgba(0,0,0,0)"
+ }),
+ this.mobileTextContainer({},
+ [this.mobileButtonText({}, "NEW MOBILE BUTTON"), this.mobileButtonInfo({}, "You can customize this button and make it your own.")])]);
+ doc["template-mobile-button"] = CurrentUserUtils.ficon({
+ onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
+ dragFactory: new PrefetchProxy(queryTemplate) as any as Doc,
+ removeDropProperties: new List<string>(["dropAction"]), title: "mobile button", icon: "mobile"
+ });
+ }
if (doc["template-button-slides"] === undefined) {
const slideTemplate = Docs.Create.MultirowDocument(
@@ -219,6 +238,7 @@ export class CurrentUserUtils {
doc["template-button-slides"] as Doc,
doc["template-button-description"] as Doc,
doc["template-button-query"] as Doc,
+ doc["template-mobile-button"] as Doc,
doc["template-button-detail"] as Doc,
doc["template-button-link"] as Doc,
doc["template-button-switch"] as Doc];
@@ -343,7 +363,7 @@ export class CurrentUserUtils {
} else {
const templateIconsDoc = Cast(doc["template-icons"], Doc, null);
const requiredTypes = [doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc,
- doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc, doc["template-icon-view-pdf"] as Doc];
+ doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc];
DocListCastAsync(templateIconsDoc.data).then(async curIcons => {
await Promise.all(curIcons!);
requiredTypes.map(ntype => Doc.AddDocToList(templateIconsDoc, "data", ntype));
@@ -364,16 +384,25 @@ export class CurrentUserUtils {
doc.emptyCollection = Docs.Create.FreeformDocument([],
{ _nativeWidth: undefined, _nativeHeight: undefined, _width: 150, _height: 100, title: "freeform" });
}
+ if (doc.emptyComparison === undefined) {
+ doc.emptyComparison = Docs.Create.ComparisonDocument({ title: "compare", _width: 300, _height: 300 });
+ }
+ if (doc.emptyScript === undefined) {
+ doc.emptyScript = Docs.Create.ScriptingDocument(undefined, { _width: 200, _height: 250, title: "script" });
+ }
if (doc.emptyDocHolder === undefined) {
doc.emptyDocHolder = Docs.Create.DocumentDocument(
ComputedField.MakeFunction("selectedDocs(this,this.excludeCollections,[_last_])?.[0]") as any,
{ _width: 250, _height: 250, title: "container" });
}
if (doc.emptyWebpage === undefined) {
- doc.emptyWebpage = Docs.Create.WebDocument("", { title: "New Webpage", _nativeWidth: 850, _nativeHeight: 962, _width: 600, UseCors: true });
+ doc.emptyWebpage = Docs.Create.WebDocument("", { title: "webpage", _nativeWidth: 850, _nativeHeight: 962, _width: 600, UseCors: true });
+ }
+ if (doc.activeMobileMenu === undefined) {
+ this.setupActiveMobileMenu(doc);
}
return [
- { title: "Drag a comparison box", label: "Comp", icon: "columns", ignoreClick: true, drag: 'Docs.Create.ComparisonDocument()' },
+ { title: "Drag a comparison box", label: "Comp", icon: "columns", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyComparison as Doc },
{ title: "Drag a collection", label: "Col", icon: "folder", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyCollection as Doc },
{ title: "Drag a web page", label: "Web", icon: "globe-asia", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyWebpage as Doc },
{ title: "Drag a cat image", label: "Img", icon: "cat", ignoreClick: true, drag: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { _width: 250, _nativeWidth:250, title: "an image of a cat" })' },
@@ -383,9 +412,9 @@ export class CurrentUserUtils {
{ title: "Drag a clickable button", label: "Btn", icon: "bolt", ignoreClick: true, drag: 'Docs.Create.ButtonDocument({ _width: 150, _height: 50, _xPadding:10, _yPadding: 10, title: "Button" })' },
{ title: "Drag a presentation view", label: "Prezi", icon: "tv", click: 'openOnRight(Doc.UserDoc().activePresentation = getCopy(this.dragFactory, true))', drag: `Doc.UserDoc().activePresentation = getCopy(this.dragFactory,true)`, dragFactory: doc.emptyPresentation as Doc },
{ title: "Drag a search box", label: "Query", icon: "search", ignoreClick: true, drag: 'Docs.Create.QueryDocument({ _width: 200, title: "an image of a cat" })' },
- { title: "Drag a scripting box", label: "Script", icon: "terminal", ignoreClick: true, drag: 'Docs.Create.ScriptingDocument(undefined, { _width: 200, _height: 250 title: "untitled script" })' },
+ { title: "Drag a scripting box", label: "Script", icon: "terminal", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyScript as Doc },
{ title: "Drag an import folder", label: "Load", icon: "cloud-upload-alt", ignoreClick: true, drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", _width: 400, _height: 400 })' },
- { title: "Drag a mobile view", label: "Phone", icon: "phone", ignoreClick: true, drag: 'Doc.UserDoc().activeMobile' },
+ { title: "Drag a mobile view", label: "Phone", icon: "mobile", click: 'openOnRight(Doc.UserDoc().activeMobileMenu)', drag: 'this.dragFactory', dragFactory: doc.activeMobileMenu as Doc },
{ title: "Drag an instance of the device collection", label: "Buxton", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.Buxton()' },
// { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this)', backgroundColor: "blue", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc },
// { title: "use highlighter", icon: "highlighter", click: 'activateBrush(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc },
@@ -439,25 +468,72 @@ export class CurrentUserUtils {
return doc.myItemCreators as Doc;
}
- static setupMobileButtons(doc: Doc, buttons?: string[]) {
- const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, ischecked?: string, activeInkPen?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [
- { title: "record", icon: "microphone", ignoreClick: true, click: "FILL" },
- { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this)', backgroundColor: "blue", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc },
- { title: "use highlighter", icon: "highlighter", click: 'activateBrush(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc },
- { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this);', ischecked: `sameDocs(this.activeInkPen, this)`, backgroundColor: "pink", activeInkPen: doc },
- { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activeInkPen = this;', ischecked: `sameDocs(this.activeInkPen, this)`, backgroundColor: "white", activeInkPen: doc },
- // { title: "draw", icon: "pen-nib", click: 'switchMobileView(setupMobileInkingDoc, renderMobileInking, onSwitchMobileInking);', ischecked: `sameDocs(this.activeInkPen, this)`, backgroundColor: "red", activeInkPen: doc },
- { title: "upload", icon: "upload", click: 'switchMobileView(setupMobileUploadDoc, renderMobileUpload, onSwitchMobileUpload);', backgroundColor: "orange" },
- // { title: "upload", icon: "upload", click: 'uploadImageMobile();', backgroundColor: "cyan" },
- ];
- return docProtoData.filter(d => !buttons || !buttons.includes(d.title)).map(data => Docs.Create.FontIconDocument({
- _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, dropAction: data.click ? "copy" : undefined, title: data.title, icon: data.icon, ignoreClick: data.ignoreClick,
- onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, onClick: data.click ? ScriptField.MakeScript(data.click) : undefined,
- ischecked: data.ischecked ? ComputedField.MakeFunction(data.ischecked) : undefined, activeInkPen: data.activeInkPen,
- backgroundColor: data.backgroundColor, removeDropProperties: new List<string>(["dropAction"]), dragFactory: data.dragFactory,
+ // Sets up mobile menu if it is undefined creates a new one, otherwise returns existing menu
+ static setupActiveMobileMenu(doc: Doc) {
+ if (doc.activeMobileMenu === undefined) {
+ console.log("undefined");
+ doc.activeMobileMenu = this.setupMobileMenu();
+ }
+ return doc.activeMobileMenu as Doc;
+ }
+
+ // Sets up mobileMenu stacking document
+ static setupMobileMenu() {
+ const menu = new PrefetchProxy(Docs.Create.StackingDocument(this.setupMobileButtons(), {
+ _width: 980, ignoreClick: true, lockedPosition: false, _chromeStatus: "disabled", title: "home", _yMargin: 100
}));
+ return menu;
+ }
+
+ // SEts up mobile buttons for inside mobile menu
+ static setupMobileButtons(doc?: Doc, buttons?: string[]) {
+ const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, ischecked?: string, activePen?: Doc, backgroundColor?: string, info: string, dragFactory?: Doc }[] = [
+ { title: "WORKSPACES", icon: "bars", click: 'switchToMobileLibrary()', backgroundColor: "lightgrey", info: "Access your Workspaces from your mobile, and navigate through all of your documents. " },
+ { title: "UPLOAD", icon: "upload", click: 'openMobileUploads()', backgroundColor: "lightgrey", info: "Upload files from your mobile device so they can be accessed on Dash Web." },
+ { title: "MOBILE UPLOAD", icon: "mobile", click: 'switchToMobileUploadCollection()', backgroundColor: "lightgrey", info: "Access the collection of your mobile uploads." },
+ { title: "RECORD", icon: "microphone", click: 'openMobileAudio()', backgroundColor: "lightgrey", info: "Use your phone to record, dictate and then upload audio onto Dash Web." },
+ { title: "PRESENTATION", icon: "desktop", click: 'switchToMobilePresentation()', backgroundColor: "lightgrey", info: "Use your phone as a remote for you presentation." },
+ { title: "SETTINGS", icon: "cog", click: 'openMobileSettings()', backgroundColor: "lightgrey", info: "Change your password, log out, or manage your account security." }
+ ];
+ // returns a list of mobile buttons
+ return docProtoData.filter(d => !buttons || !buttons.includes(d.title)).map(data =>
+ this.mobileButton({
+ title: data.title,
+ lockedPosition: true,
+ onClick: data.click ? ScriptField.MakeScript(data.click) : undefined,
+ _backgroundColor: data.backgroundColor
+ },
+ [this.ficon({ ignoreClick: true, icon: data.icon, backgroundColor: "rgba(0,0,0,0)" }), this.mobileTextContainer({}, [this.mobileButtonText({}, data.title), this.mobileButtonInfo({}, data.info)])])
+ );
}
+ // sets up the main document for the mobile button
+ static mobileButton = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.MulticolumnDocument(docs, {
+ ...opts,
+ dropAction: undefined, removeDropProperties: new List<string>(["dropAction"]), _nativeWidth: 900, _nativeHeight: 250, _width: 900, _height: 250, _yMargin: 15,
+ borderRounding: "5px", boxShadow: "0 0", _chromeStatus: "disabled"
+ }) as any as Doc
+
+ // sets up the text container for the information contained within the mobile button
+ static mobileTextContainer = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.MultirowDocument(docs, {
+ ...opts,
+ dropAction: undefined, removeDropProperties: new List<string>(["dropAction"]), _nativeWidth: 450, _nativeHeight: 250, _width: 450, _height: 250, _yMargin: 25,
+ backgroundColor: "rgba(0,0,0,0)", borderRounding: "0", boxShadow: "0 0", _chromeStatus: "disabled", ignoreClick: true
+ }) as any as Doc
+
+ // Sets up the title of the button
+ static mobileButtonText = (opts: DocumentOptions, buttonTitle: string) => Docs.Create.TextDocument(buttonTitle, {
+ ...opts,
+ dropAction: undefined, title: buttonTitle, _fontSize: 37, _xMargin: 0, _yMargin: 0, ignoreClick: true, _chromeStatus: "disabled", backgroundColor: "rgba(0,0,0,0)"
+ }) as any as Doc
+
+ // Sets up the description of the button
+ static mobileButtonInfo = (opts: DocumentOptions, buttonInfo: string) => Docs.Create.TextDocument(buttonInfo, {
+ ...opts,
+ dropAction: undefined, title: "info", _fontSize: 25, _xMargin: 0, _yMargin: 0, ignoreClick: true, _chromeStatus: "disabled", backgroundColor: "rgba(0,0,0,0)", _dimMagnitude: 2,
+ }) as any as Doc
+
+
static setupThumbButtons(doc: Doc) {
const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, pointerDown?: string, pointerUp?: string, ischecked?: string, clipboard?: Doc, activeInkPen?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [
{ title: "use pen", icon: "pen-nib", pointerUp: "resetPen()", pointerDown: 'setPen(2, this.backgroundColor)', backgroundColor: "blue", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc },
@@ -491,31 +567,12 @@ export class CurrentUserUtils {
return Cast(userDoc.thumbDoc, Doc);
}
- static setupMobileDoc(userDoc: Doc) {
- return userDoc.activeMoble ?? Docs.Create.MasonryDocument(CurrentUserUtils.setupMobileButtons(userDoc), {
- _columnWidth: 100, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", title: "buttons", _autoHeight: true, _yMargin: 5
- });
+ static setupLibrary(userDoc: Doc) {
+ return CurrentUserUtils.setupWorkspaces(userDoc);
}
- static setupMobileInkingDoc(userDoc: Doc) {
- return Docs.Create.FreeformDocument([], { title: "Mobile Inking", backgroundColor: "white" });
- }
-
- static setupMobileUploadDoc(userDoc: Doc) {
- // const addButton = Docs.Create.FontIconDocument({ onDragStart: ScriptField.MakeScript('addWebToMobileUpload()'), title: "Add Web Doc to Upload Collection", icon: "plus", backgroundColor: "black" })
- const webDoc = Docs.Create.WebDocument("https://www.britannica.com/biography/Miles-Davis", {
- title: "Upload Images From the Web", _chromeStatus: "enabled", lockedPosition: true
- });
- const uploadDoc = Docs.Create.StackingDocument([], {
- title: "Mobile Upload Collection", backgroundColor: "white", lockedPosition: true
- });
- return Docs.Create.StackingDocument([webDoc, uploadDoc], {
- _width: screen.width, lockedPosition: true, _chromeStatus: "disabled", title: "Upload", _autoHeight: true, _yMargin: 80, backgroundColor: "lightgray"
- });
- }
-
- // setup the Creator button which will display the creator panel. This panel will include the drag creators and the color picker.
- // when clicked, this panel will be displayed in the target container (ie, sidebarContainer)
+ // setup the Creator button which will display the creator panel. This panel will include the drag creators and the color picker.
+ // when clicked, this panel will be displayed in the target container (ie, sidebarContainer)
static async setupToolsBtnPanel(doc: Doc, sidebarContainer: Doc) {
// setup a masonry view of all he creators
const creatorBtns = await CurrentUserUtils.setupCreatorButtons(doc);
@@ -619,7 +676,7 @@ export class CurrentUserUtils {
return doc["tabs-button-library"] as Doc;
}
- // setup the Search button which will display the search panel.
+ // setup the Search button which will display the search panel.
static setupSearchBtnPanel(doc: Doc, sidebarContainer: Doc) {
if (doc["tabs-button-search"] === undefined) {
doc["tabs-button-search"] = new PrefetchProxy(Docs.Create.ButtonDocument({
@@ -709,12 +766,14 @@ export class CurrentUserUtils {
}
}
+ // Right sidebar is where mobile uploads are contained
static setupRightSidebar(doc: Doc) {
if (doc.rightSidebarCollection === undefined) {
- doc.rightSidebarCollection = new PrefetchProxy(Docs.Create.StackingDocument([], { title: "Right Sidebar" }));
+ doc.rightSidebarCollection = new PrefetchProxy(Docs.Create.StackingDocument([], { title: "Mobile Uploads" }));
}
}
+
static setupClickEditorTemplates(doc: Doc) {
if (doc["clickFuncs-child"] === undefined) {
const openInTarget = Docs.Create.ScriptingDocument(ScriptField.MakeScript(
@@ -765,13 +824,14 @@ export class CurrentUserUtils {
doc.activeArrowEnd = StrCast(doc.activeArrowEnd, "none");
doc.activeDash = StrCast(doc.activeDash, "0");
doc.fontSize = NumCast(doc.fontSize, 12);
- doc["constants-snapThreshold"] = NumCast(doc["constants-snapThreshold"], 10); //
- doc["constants-dragThreshold"] = NumCast(doc["constants-dragThreshold"], 4); //
+ doc["constants-snapThreshold"] = NumCast(doc["constants-snapThreshold"], 10); //
+ doc["constants-dragThreshold"] = NumCast(doc["constants-dragThreshold"], 4); //
Utils.DRAG_THRESHOLD = NumCast(doc["constants-dragThreshold"]);
this.setupDefaultIconTemplates(doc); // creates a set of icon templates triggered by the document deoration icon
this.setupDocTemplates(doc); // sets up the template menu of templates
this.setupRightSidebar(doc); // sets up the right sidebar collection for mobile upload documents and sharing
- this.setupOverlays(doc); // documents in overlay layer
+ this.setupActiveMobileMenu(doc); // sets up the current mobile menu for Dash Mobile
+ this.setupOverlays(doc); // documents in overlay layer
this.setupDockedButtons(doc); // the bottom bar of font icons
this.setupDefaultPresentation(doc); // presentation that's initially triggered
await this.setupSidebarButtons(doc); // the pop-out left sidebar of tools/panels
@@ -810,9 +870,5 @@ export class CurrentUserUtils {
}
}
-Scripting.addGlobal(function setupMobileInkingDoc(userDoc: Doc) { return CurrentUserUtils.setupMobileInkingDoc(userDoc); },
- "initializes the Mobile inking document", "(userDoc: Doc)");
-Scripting.addGlobal(function setupMobileUploadDoc(userDoc: Doc) { return CurrentUserUtils.setupMobileUploadDoc(userDoc); },
- "initializes the Mobile upload document", "(userDoc: Doc)");
Scripting.addGlobal(function createNewWorkspace() { return MainView.Instance.createNewWorkspace(); },
- "creates a new workspace when called"); \ No newline at end of file
+ "creates a new workspace when called");
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 55ee5b4cf..e43c3acb9 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -146,7 +146,7 @@ export class DocumentManager {
};
const docView = getFirstDocView(targetDoc, originatingDoc);
let annotatedDoc = await Cast(targetDoc.annotationOn, Doc);
- if (annotatedDoc) {
+ if (annotatedDoc && !linkDoc?.isPushpin) {
const first = getFirstDocView(annotatedDoc);
if (first) {
annotatedDoc = first.props.Document;
@@ -156,7 +156,11 @@ export class DocumentManager {
}
}
if (docView) { // we have a docView already and aren't forced to create a new one ... just focus on the document. TODO move into view if necessary otherwise just highlight?
- docView.props.focus(docView.props.Document, willZoom, undefined, focusAndFinish);
+ if (linkDoc?.isPushpin) docView.props.Document.hidden = !docView.props.Document.hidden;
+ else {
+ docView.props.Document.hidden && (docView.props.Document.hidden = undefined);
+ docView.props.focus(docView.props.Document, willZoom, undefined, focusAndFinish);
+ }
highlight();
} else {
const contextDocs = docContext ? await DocListCastAsync(docContext.data) : undefined;
diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index edeb461e0..02b444cd3 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -27,7 +27,7 @@ export namespace InteractionUtils {
export interface MultiTouchEventDisposer { (): void; }
/**
- *
+ *
* @param element - element to turn into a touch target
* @param startFunc - event handler, typically Touchable.onTouchStart (classes that inherit touchable can pass in this.onTouchStart)
*/
@@ -278,8 +278,8 @@ export namespace InteractionUtils {
/**
* Returns euclidean distance between two points
- * @param pt1
- * @param pt2
+ * @param pt1
+ * @param pt2
*/
export function TwoPointEuclidist(pt1: React.Touch, pt2: React.Touch): number {
return Math.sqrt(Math.pow(pt1.clientX - pt2.clientX, 2) + Math.pow(pt1.clientY - pt2.clientY, 2));
@@ -410,4 +410,4 @@ export namespace InteractionUtils {
// }
// }
}
-} \ No newline at end of file
+}
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 9b4dc2630..749fabfcc 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -49,7 +49,7 @@ export class LinkManager {
}
public deleteLink(linkDoc: Doc): boolean {
- if (LinkManager.Instance.LinkManagerDoc) {
+ if (LinkManager.Instance.LinkManagerDoc && linkDoc instanceof Doc) {
Doc.RemoveDocFromList(LinkManager.Instance.LinkManagerDoc, "data", linkDoc);
return true;
}
diff --git a/src/client/util/SettingsManager.scss b/src/client/util/SettingsManager.scss
index fa2609ca2..13c65042c 100644
--- a/src/client/util/SettingsManager.scss
+++ b/src/client/util/SettingsManager.scss
@@ -134,4 +134,25 @@
}
+}
+
+@media only screen and (max-device-width: 480px) {
+ .settings-interface {
+ width: 80vw;
+ height: 400px;
+ }
+
+ .settings-interface .settings-body .settings-content input {
+ font-size: 30;
+ }
+
+ .settings-interface button {
+ width: 100%;
+ font-size: 30px;
+ background: #b2cef8;
+ }
+
+ .settings-interface .settings-heading {
+ font-size: 25;
+ }
} \ No newline at end of file
diff --git a/src/client/views/AntimodeMenu.scss b/src/client/views/AntimodeMenu.scss
index e56574bb7..be21cec12 100644
--- a/src/client/views/AntimodeMenu.scss
+++ b/src/client/views/AntimodeMenu.scss
@@ -42,4 +42,35 @@
background-repeat: no-repeat;
background-position: left center;
}
+}
+
+@media only screen and (max-device-width: 480px) {
+ .antimodeMenu-cont {
+ height: 100px;
+ width: 100vw;
+
+ &.with-rows {
+ flex-direction: column-reverse;
+ }
+
+ .antimodeMenu-row {
+ display: flex;
+ height: 100%;
+ width: 100%;
+ }
+
+ .antimodeMenu-button {
+ background-color: transparent;
+ width: 100px;
+ height: 100px;
+
+ &.active {
+ background-color: #121212;
+ }
+ }
+
+ .antimodeMenu-button:hover {
+ background-color: #121212;
+ }
+ }
} \ No newline at end of file
diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx
index 3e4d20fea..cb293dee4 100644
--- a/src/client/views/AntimodeMenu.tsx
+++ b/src/client/views/AntimodeMenu.tsx
@@ -51,18 +51,15 @@ export default abstract class AntimodeMenu extends React.Component {
if (this._opacity === 0.2) {
this._transitionProperty = "opacity";
this._transitionDuration = "0.1s";
- this._transitionDelay = "";
- this._opacity = 0;
- this._left = this._top = -300;
}
if (forceOut) {
this._transitionProperty = "";
this._transitionDuration = "";
- this._transitionDelay = "";
- this._opacity = 0;
- this._left = this._top = -300;
}
+ this._transitionDelay = "";
+ this._opacity = 0;
+ this._left = this._top = -300;
}
}
@@ -131,7 +128,7 @@ export default abstract class AntimodeMenu extends React.Component {
}
protected getDragger = () => {
- return <div className="antimodeMenu-dragger" onPointerDown={this.dragStart} style={{ width: "20px" }} />;
+ return <div className="antimodeMenu-dragger" key="dragger" onPointerDown={this.dragStart} style={{ width: "20px" }} />;
}
protected getElement(buttons: JSX.Element[]) {
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 941d7b44a..07f7b8e6d 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -155,9 +155,11 @@ export class ContextMenu extends React.Component {
@action
closeMenu = () => {
+ const wasOpen = this._display;
this.clearItems();
this._display = false;
this._shouldDisplay = false;
+ return wasOpen;
}
@computed get filteredItems(): (OriginalMenuProps | string[])[] {
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index c05ca33fb..1667b2f65 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -271,7 +271,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
const considerPush = isText && this.considerGoogleDocsPush;
return <div className="documentButtonBar">
<div className="documentButtonBar-button">
- <DocumentLinksButton View={this.view0} AlwaysOn={true} />
+ <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} />
</div>
<div className="documentButtonBar-button">
{this.templateButton}
@@ -293,4 +293,4 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
</div>
</div>;
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 4fda10926..a45ef8862 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -600,52 +600,54 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
zIndex: SelectionManager.SelectedDocuments().length > 1 ? 900 : 0,
}} onPointerDown={this.onBackgroundDown} onContextMenu={e => { e.preventDefault(); e.stopPropagation(); }} >
</div>
- <div className="documentDecorations-container" ref={this.setTextBar} style={{
- width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
- height: (bounds.b - bounds.y + this._resizeBorderWidth + this._titleHeight) + "px",
- left: bounds.x - this._resizeBorderWidth / 2,
- top: bounds.y - this._resizeBorderWidth / 2 - this._titleHeight,
- }}>
- {maximizeIcon}
- {titleArea}
- {SelectionManager.SelectedDocuments().length !== 1 || seldoc.Document.type === DocumentType.INK ? (null) :
- <div className="documentDecorations-iconifyButton" title={`${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`} onPointerDown={this.onIconifyDown}>
- {"_"}
- </div>}
- <div className="documentDecorations-closeButton" title="Open Document in Tab" onPointerDown={this.onMaximizeDown}>
- {SelectionManager.SelectedDocuments().length === 1 ? DocumentDecorations.DocumentIcon(StrCast(seldoc.props.Document.layout, "...")) : "..."}
+ {bounds.r - bounds.x < 15 && bounds.b - bounds.y < 15 ? (null) : <>
+ <div className="documentDecorations-container" key="container" ref={this.setTextBar} style={{
+ width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
+ height: (bounds.b - bounds.y + this._resizeBorderWidth + this._titleHeight) + "px",
+ left: bounds.x - this._resizeBorderWidth / 2,
+ top: bounds.y - this._resizeBorderWidth / 2 - this._titleHeight,
+ }}>
+ {maximizeIcon}
+ {titleArea}
+ {SelectionManager.SelectedDocuments().length !== 1 || seldoc.Document.type === DocumentType.INK ? (null) :
+ <div className="documentDecorations-iconifyButton" title={`${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`} onPointerDown={this.onIconifyDown}>
+ {"_"}
+ </div>}
+ <div className="documentDecorations-closeButton" title="Open Document in Tab" onPointerDown={this.onMaximizeDown}>
+ {SelectionManager.SelectedDocuments().length === 1 ? DocumentDecorations.DocumentIcon(StrCast(seldoc.props.Document.layout, "...")) : "..."}
+ </div>
+ <div id="documentDecorations-rotation" className="documentDecorations-rotation"
+ onPointerDown={this.onRotateDown}> ⟲ </div>
+ <div id="documentDecorations-topLeftResizer" className="documentDecorations-resizer"
+ onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+ <div id="documentDecorations-topResizer" className="documentDecorations-resizer"
+ onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+ <div id="documentDecorations-topRightResizer" className="documentDecorations-resizer"
+ onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+ <div id="documentDecorations-leftResizer" className="documentDecorations-resizer"
+ onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+ <div id="documentDecorations-centerCont"></div>
+ <div id="documentDecorations-rightResizer" className="documentDecorations-resizer"
+ onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+ <div id="documentDecorations-bottomLeftResizer" className="documentDecorations-resizer"
+ onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+ <div id="documentDecorations-bottomResizer" className="documentDecorations-resizer"
+ onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+ <div id="documentDecorations-bottomRightResizer" className="documentDecorations-resizer"
+ onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+ {seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? (null) :
+ <div id="documentDecorations-levelSelector" className="documentDecorations-selector"
+ title="tap to select containing document" onPointerDown={this.onSelectorUp} onContextMenu={e => e.preventDefault()}>
+ <FontAwesomeIcon className="documentdecorations-times" icon={faArrowAltCircleUp} size="lg" />
+ </div>}
+ <div id="documentDecorations-borderRadius" className="documentDecorations-radius"
+ onPointerDown={this.onRadiusDown} onContextMenu={(e) => e.preventDefault()}></div>
+
+ </div >
+ <div className="link-button-container" key="links" style={{ left: bounds.x - this._resizeBorderWidth / 2 + 10, top: bounds.b + this._resizeBorderWidth / 2 }}>
+ <DocumentButtonBar views={SelectionManager.SelectedDocuments} />
</div>
- <div id="documentDecorations-rotation" className="documentDecorations-rotation"
- onPointerDown={this.onRotateDown}> ⟲ </div>
- <div id="documentDecorations-topLeftResizer" className="documentDecorations-resizer"
- onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
- <div id="documentDecorations-topResizer" className="documentDecorations-resizer"
- onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
- <div id="documentDecorations-topRightResizer" className="documentDecorations-resizer"
- onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
- <div id="documentDecorations-leftResizer" className="documentDecorations-resizer"
- onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
- <div id="documentDecorations-centerCont"></div>
- <div id="documentDecorations-rightResizer" className="documentDecorations-resizer"
- onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
- <div id="documentDecorations-bottomLeftResizer" className="documentDecorations-resizer"
- onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
- <div id="documentDecorations-bottomResizer" className="documentDecorations-resizer"
- onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
- <div id="documentDecorations-bottomRightResizer" className="documentDecorations-resizer"
- onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
- {seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? (null) :
- <div id="documentDecorations-levelSelector" className="documentDecorations-selector"
- title="tap to select containing document" onPointerDown={this.onSelectorUp} onContextMenu={e => e.preventDefault()}>
- <FontAwesomeIcon className="documentdecorations-times" icon={faArrowAltCircleUp} size="lg" />
- </div>}
- <div id="documentDecorations-borderRadius" className="documentDecorations-radius"
- onPointerDown={this.onRadiusDown} onContextMenu={(e) => e.preventDefault()}></div>
-
- </div >
- <div className="link-button-container" style={{ left: bounds.x - this._resizeBorderWidth / 2 + 10, top: bounds.b + this._resizeBorderWidth / 2 }}>
- <DocumentButtonBar views={SelectionManager.SelectedDocuments} />
- </div>
+ </>}
</div >
);
}
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index bab3a1634..628db366f 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -194,7 +194,6 @@ export class EditableView extends React.Component<EditableProps> {
ref={this._ref}
style={{ display: this.props.display, minHeight: "20px", height: `${this.props.height ? this.props.height : "auto"}`, maxHeight: `${this.props.maxHeight}` }}
onClick={this.onClick} placeholder={this.props.placeholder}>
-
<span style={{ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize, color: this.props.contents ? "black" : "grey" }}>{this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()}</span>
</div>
);
diff --git a/src/client/views/GestureOverlay.scss b/src/client/views/GestureOverlay.scss
index 107077792..f61f4a05e 100644
--- a/src/client/views/GestureOverlay.scss
+++ b/src/client/views/GestureOverlay.scss
@@ -1,7 +1,7 @@
.gestureOverlay-cont {
width: 100vw;
height: 100vh;
- position: absolute;
+ position: fixed;
top: 0;
left: 0;
touch-action: none;
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 85695bbac..487467b2b 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -5,7 +5,6 @@ import { Doc } from "../../fields/Doc";
import { InkData, InkTool } from "../../fields/InkField";
import { Cast, FieldValue, NumCast } from "../../fields/Types";
import MobileInkOverlay from "../../mobile/MobileInkOverlay";
-import MobileInterface from "../../mobile/MobileInterface";
import { GestureUtils } from "../../pen-gestures/GestureUtils";
import { MobileInkOverlayContent } from "../../server/Message";
import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero, returnEmptyFilter } from "../../Utils";
@@ -112,7 +111,7 @@ export default class GestureOverlay extends Touchable {
onReactTouchStart = (te: React.TouchEvent) => {
document.removeEventListener("touchmove", this.onReactHoldTouchMove);
document.removeEventListener("touchend", this.onReactHoldTouchEnd);
- if (RadialMenu.Instance._display === true) {
+ if (RadialMenu.Instance?._display === true) {
te.preventDefault();
te.stopPropagation();
RadialMenu.Instance.closeMenu();
@@ -162,8 +161,8 @@ export default class GestureOverlay extends Touchable {
// -- radial menu code --
this._holdTimer = setTimeout(() => {
console.log("hold");
- const target = document.elementFromPoint(te.changedTouches.item(0).clientX, te.changedTouches.item(0).clientY);
- const pt: any = te.touches[te.touches.length - 1];
+ const target = document.elementFromPoint(te.changedTouches?.item(0).clientX, te.changedTouches?.item(0).clientY);
+ const pt: any = te.touches[te.touches?.length - 1];
if (nts.nt.length === 1 && pt.radiusX > 1 && pt.radiusY > 1) {
target?.dispatchEvent(
new CustomEvent<InteractionUtils.MultiTouchEvent<React.TouchEvent>>("dashOnTouchHoldStart",
@@ -580,14 +579,6 @@ export default class GestureOverlay extends Touchable {
const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top }));
//push first points to so interactionUtil knows pointer is up
this._points.push({ X: this._points[0].X, Y: this._points[0].Y });
- if (MobileInterface.Instance && MobileInterface.Instance.drawingInk) {
- DocServer.Mobile.dispatchGesturePoints({
- points: this._points,
- bounds: B,
- color: ActiveInkColor(),
- width: ActiveInkWidth()
- });
- }
const initialPoint = this._points[0.];
const xInGlass = initialPoint.X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && initialPoint.X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + (this.height);
@@ -725,7 +716,7 @@ export default class GestureOverlay extends Touchable {
this._points = [];
switch (shape) {
//must push an extra point in the end so InteractionUtils knows pointer is up.
- //must be (points[0].X,points[0]-1)
+ //must be (points[0].X,points[0]-1)
case "rectangle":
this._points.push({ X: left, Y: top });
this._points.push({ X: right, Y: top });
@@ -912,7 +903,7 @@ export default class GestureOverlay extends Touchable {
}
}
-// export class
+// export class
export enum ToolglassTools {
InkToText = "inktotext",
@@ -949,4 +940,4 @@ Scripting.addGlobal(function resetPen() {
}, "resets the pen tool");
Scripting.addGlobal(function createText(text: any, x: any, y: any) {
GestureOverlay.Instance.dispatchGesture("text", [{ X: x, Y: y }], text);
-}, "creates a text document with inputted text and coordinates", "(text: any, x: any, y: any)"); \ No newline at end of file
+}, "creates a text document with inputted text and coordinates", "(text: any, x: any, y: any)");
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index e3546dece..34f666f62 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -20,6 +20,7 @@ import { MainView } from "./MainView";
import { DocumentView } from "./nodes/DocumentView";
import { DocumentLinksButton } from "./nodes/DocumentLinksButton";
import PDFMenu from "./pdf/PDFMenu";
+import { ContextMenu } from "./ContextMenu";
const modifiers = ["control", "meta", "shift", "alt"];
type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise<KeyControlInfo>;
@@ -78,19 +79,28 @@ export default class KeyManager {
// MarqueeView.DragMarquee = !MarqueeView.DragMarquee; // bcz: this needs a better disclosure UI
break;
case "escape":
+ // if (DocumentLinksButton.StartLink) {
+ // if (DocumentLinksButton.StartLink.Document) {
+ // action((e: React.PointerEvent<HTMLDivElement>) => {
+ // Doc.UnBrushDoc(DocumentLinksButton.StartLink?.Document as Doc);
+ // });
+ // }
+ // }
DocumentLinksButton.StartLink = undefined;
+
const main = MainView.Instance;
Doc.SetSelectedTool(InkTool.None);
+ var doDeselect = true;
if (main.isPointerDown) {
DragManager.AbortDrag();
} else {
if (CollectionDockingView.Instance.HasFullScreen()) {
CollectionDockingView.Instance.CloseFullScreen();
} else {
- SelectionManager.DeselectAll();
+ doDeselect = !ContextMenu.Instance.closeMenu();
}
}
- SelectionManager.DeselectAll();
+ doDeselect && SelectionManager.DeselectAll();
DictationManager.Controls.stop();
// RecommendationsBox.Instance.closeMenu();
GoogleAuthenticationManager.Instance.cancel();
@@ -333,4 +343,4 @@ export default class KeyManager {
};
});
-} \ No newline at end of file
+}
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index c32e76cec..7d6e7e5dd 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -72,8 +72,8 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
overflow: "visible",
}}
onContextMenu={() => {
- ContextMenu.Instance.addItem({ description: "Analyze Stroke", event: this.analyzeStrokes, icon: "paint-brush" });
- ContextMenu.Instance.addItem({ description: "Make Mask", event: this.makeMask, icon: "paint-brush" });
+ ContextMenu.Instance?.addItem({ description: "Analyze Stroke", event: this.analyzeStrokes, icon: "paint-brush" });
+ ContextMenu.Instance?.addItem({ description: "Make Mask", event: this.makeMask, icon: "paint-brush" });
}}
><defs>
</defs>
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index d96bcfba9..e961e2e5c 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -4,8 +4,9 @@ import {
faTerminal, faToggleOn, faFile as fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard,
faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
- faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote,
- faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAngleDown, faAngleUp, faSearchPlus
+ faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTimesCircle,
+ faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight,
+ faAngleDown, faAngleUp, faSearchPlus
} from '@fortawesome/free-solid-svg-icons';
import { ANTIMODEMENU_HEIGHT } from './globalCssVariables.scss';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -59,6 +60,8 @@ import { DocumentManager } from '../util/DocumentManager';
import { DocumentLinksButton } from './nodes/DocumentLinksButton';
import { LinkMenu } from './linking/LinkMenu';
import { LinkDocPreview } from './nodes/LinkDocPreview';
+import { Fade } from '@material-ui/core';
+import { LinkCreatedBox } from './nodes/LinkCreatedBox';
@observer
export class MainView extends React.Component {
@@ -82,7 +85,6 @@ export class MainView extends React.Component {
public isPointerDown = false;
-
componentDidMount() {
DocServer.setPlaygroundFields(["dataTransition", "_viewTransition", "_panX", "_panY", "_viewScale", "_viewType"]); // can play with these fields on someone else's
@@ -99,7 +101,7 @@ export class MainView extends React.Component {
window.removeEventListener("keydown", KeyManager.Instance.handle);
window.addEventListener("keydown", KeyManager.Instance.handle);
window.addEventListener("paste", KeyManager.Instance.paste as any);
- document.addEventListener("dash", (e: any) => { // event used by chrome plugin to tell Dash which document to focus on
+ document.addEventListener("dash", (e: any) => { // event used by chrome plugin to tell Dash which document to focus on
const id = FormattedTextBox.GetDocFromUrl(e.detail);
DocServer.GetRefField(id).then(doc => {
if (doc instanceof Doc) {
@@ -138,11 +140,11 @@ export class MainView extends React.Component {
}
library.add(faTasks, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
- faTerminal, faToggleOn, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard, fileSolid,
+ faTerminal, faToggleOn, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faTimesCircle, faWindowMaximize, faAddressCard, fileSolid,
faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTrashAlt, faAngleRight, faBell,
- faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAngleDown, faAngleUp, faSearchPlus);
+ faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight, faAngleDown, faAngleUp, faSearchPlus);
this.initEventListeners();
this.initAuthenticationRouters();
}
@@ -606,6 +608,7 @@ export class MainView extends React.Component {
{this.mainContent}
</GestureOverlay>
<PreviewCursor />
+ <LinkCreatedBox />
{DocumentLinksButton.EditLink ? <LinkMenu location={DocumentLinksButton.EditLinkLoc} docView={DocumentLinksButton.EditLink} addDocTab={DocumentLinksButton.EditLink.props.addDocTab} changeFlyout={emptyFunction} /> : (null)}
{LinkDocPreview.LinkInfo ? <LinkDocPreview location={LinkDocPreview.LinkInfo.Location} backgroundColor={this.defaultBackgroundColors}
linkDoc={LinkDocPreview.LinkInfo.linkDoc} linkSrc={LinkDocPreview.LinkInfo.linkSrc} href={LinkDocPreview.LinkInfo.href}
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index 1ab99881d..6583589f3 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -3,13 +3,18 @@ import { observer } from 'mobx-react';
import "normalize.css";
import * as React from 'react';
import "./PreviewCursor.scss";
-import { Docs } from '../documents/Documents';
+import { Docs, DocUtils } from '../documents/Documents';
import { Doc } from '../../fields/Doc';
import { Transform } from "../util/Transform";
import { DocServer } from '../DocServer';
-import { undoBatch } from '../util/UndoManager';
+import { undoBatch, UndoManager } from '../util/UndoManager';
import { NumCast } from '../../fields/Types';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
+import * as rp from 'request-promise';
+import { Utils } from '../../Utils';
+import { Networking } from '../Network';
+import { Upload } from '../../server/SharedMediaTypes';
+import { basename } from 'path';
@observer
export class PreviewCursor extends React.Component<{}> {
@@ -26,7 +31,7 @@ export class PreviewCursor extends React.Component<{}> {
document.addEventListener("paste", this.paste);
}
- paste = (e: ClipboardEvent) => {
+ paste = async (e: ClipboardEvent) => {
if (PreviewCursor.Visible && e.clipboardData) {
const newPoint = PreviewCursor._getTransform().transformPoint(PreviewCursor._clickPoint[0], PreviewCursor._clickPoint[1]);
runInAction(() => PreviewCursor.Visible = false);
@@ -104,6 +109,16 @@ export class PreviewCursor extends React.Component<{}> {
x: newPoint[0],
y: newPoint[1],
})))();
+ } else if (e.clipboardData.items.length) {
+ const batch = UndoManager.StartBatch("collection view drop");
+ const files: File[] = [];
+ for (let i = 0; i < e.clipboardData.items.length; i++) {
+ const file = e.clipboardData.items[i].getAsFile();
+ file && files.push(file);
+ }
+ const generatedDocuments = await DocUtils.uploadFilesToDocs(files, { x: newPoint[0], y: newPoint[1] });
+ generatedDocuments.forEach(PreviewCursor._addDocument);
+ batch.end();
}
}
}
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index f65a89422..bd0e4fc9a 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -87,7 +87,7 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
onContextMenu = (e: React.MouseEvent): void => {
// need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
if (!e.isPropagationStopped()) {
- ContextMenu.Instance.addItem({
+ ContextMenu.Instance?.addItem({
description: "Make Hero Image", event: () => {
const index = NumCast(this.layoutDoc._itemIndex);
(this.dataDoc || Doc.GetProto(this.props.Document)).hero = ObjectField.MakeCopy(this.childLayoutPairs[index].layout.data as ObjectField);
diff --git a/src/client/views/collections/CollectionLinearView.scss b/src/client/views/collections/CollectionLinearView.scss
index 123a27deb..5ada79a28 100644
--- a/src/client/views/collections/CollectionLinearView.scss
+++ b/src/client/views/collections/CollectionLinearView.scss
@@ -1,12 +1,51 @@
@import "../globalCssVariables";
@import "../_nodeModuleOverrides";
-.collectionLinearView-outer{
+.collectionLinearView-outer {
overflow: hidden;
- height:100%;
+ height: 100%;
+
.collectionLinearView {
- display:flex;
+ display: flex;
height: 100%;
+
+ >span {
+ background: $dark-color;
+ color: $light-color;
+ border-radius: 18px;
+ margin-right: 6px;
+ cursor: pointer;
+ }
+
+ .bottomPopup-background {
+ padding-right: 14px;
+ height: 35;
+ transform: translate3d(6px, 5px, 0px);
+ padding-top: 6.5px;
+ padding-bottom: 7px;
+ padding-left: 5px;
+ }
+
+ .bottomPopup-text {
+ display: inline;
+ white-space: nowrap;
+ padding-left: 8px;
+ padding-right: 4px;
+ vertical-align: middle;
+ font-size: 12.5px;
+ }
+
+ .bottomPopup-exit {
+ display: inline;
+ white-space: nowrap;
+ padding-left: 8px;
+ padding-right: 8px;
+ vertical-align: middle;
+ background-color: lightgrey;
+ border-radius: 5.5px;
+ color: black;
+ }
+
>label {
margin-top: "auto";
margin-bottom: "auto";
@@ -17,15 +56,15 @@
font-size: 12.5px;
width: 18px;
height: 18px;
- margin-top:auto;
- margin-bottom:auto;
+ margin-top: auto;
+ margin-bottom: auto;
margin-right: 3px;
cursor: pointer;
transition: transform 0.2s;
}
label p {
- padding-left:5px;
+ padding-left: 5px;
}
label:hover {
@@ -36,6 +75,7 @@
>input {
display: none;
}
+
>input:not(:checked)~.collectionLinearView-content {
display: none;
}
@@ -52,12 +92,14 @@
position: relative;
margin-top: auto;
- .collectionLinearView-docBtn, .collectionLinearView-docBtn-scalable {
- position:relative;
- margin:auto;
+ .collectionLinearView-docBtn,
+ .collectionLinearView-docBtn-scalable {
+ position: relative;
+ margin: auto;
margin-left: 3px;
transform-origin: center 80%;
}
+
.collectionLinearView-docBtn-scalable:hover {
transform: scale(1.15);
}
@@ -68,10 +110,10 @@
border-radius: 18px;
font-size: 15px;
}
-
+
.collectionLinearView-round-button:hover {
transform: scale(1.15);
}
}
}
-}
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index f38eeaf41..7cbe5c19d 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -13,6 +13,8 @@ import { CollectionSubView } from './CollectionSubView';
import { DocumentView } from '../nodes/DocumentView';
import { documentSchema } from '../../../fields/documentSchemas';
import { Id } from '../../../fields/FieldSymbols';
+import { DocumentLinksButton } from '../nodes/DocumentLinksButton';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
type LinearDocument = makeInterface<[typeof documentSchema,]>;
@@ -75,6 +77,18 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
return new Transform(-translateX, -translateY, 1);
}
+ @action
+ exitLongLinks = () => {
+ if (DocumentLinksButton.StartLink) {
+ if (DocumentLinksButton.StartLink.Document) {
+ action((e: React.PointerEvent<HTMLDivElement>) => {
+ Doc.UnBrushDoc(DocumentLinksButton.StartLink?.Document as Doc);
+ });
+ }
+ }
+ DocumentLinksButton.StartLink = undefined;
+ }
+
render() {
const guid = Utils.GenerateGuid();
const flexDir: any = StrCast(this.Document.flexDirection);
@@ -82,7 +96,12 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
const color = StrCast(this.props.Document.color, "white");
return <div className="collectionLinearView-outer">
<div className="collectionLinearView" ref={this.createDashEventsTarget} >
- <label htmlFor={`${guid}`} title="Close Menu" style={{ background: backgroundColor === color ? "black" : backgroundColor }}
+ <label htmlFor={`${guid}`} title="Close Menu" style={{
+ background: backgroundColor === color ? "black" : backgroundColor,
+ // width: "18px", height: "18px", fontSize: "12.5px",
+ // transition: this.props.Document.linearViewIsExpanded ? "transform 0.2s" : "transform 0.5s",
+ // transform: this.props.Document.linearViewIsExpanded ? "" : "rotate(45deg)"
+ }}
onPointerDown={e => e.stopPropagation()} >
<p>+</p>
</label>
@@ -130,6 +149,19 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
</div>;
})}
</div>
+ {DocumentLinksButton.StartLink ? <span className="bottomPopup-background" style={{
+ background: backgroundColor === color ? "black" : backgroundColor
+ }}
+ onPointerDown={e => e.stopPropagation()} >
+ <span className="bottomPopup-text" >
+ Creating link from: {DocumentLinksButton.StartLink.title} </span>
+ <span className="bottomPopup-exit" onClick={this.exitLongLinks}
+ >Exit</span>
+
+ {/* <FontAwesomeIcon icon="times-circle" size="lg" style={{ color: "red" }}
+ onClick={this.exitLongLinks} /> */}
+
+ </span> : null}
</div>
</div>;
}
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 2b8110e27..d76b6d204 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -1,22 +1,22 @@
import React = require("react");
-import { action, observable, trace } from "mobx";
+import { action, observable, trace, computed } from "mobx";
import { observer } from "mobx-react";
import { CellInfo } from "react-table";
import "react-table/react-table.css";
-import { emptyFunction, returnFalse, returnZero, returnOne, returnEmptyFilter } from "../../../Utils";
+import { emptyFunction, returnFalse, returnZero, returnOne, returnEmptyFilter, Utils, emptyPath } from "../../../Utils";
import { Doc, DocListCast, Field, Opt } from "../../../fields/Doc";
import { Id } from "../../../fields/FieldSymbols";
import { KeyCodes } from "../../util/KeyCodes";
import { SetupDrag, DragManager } from "../../util/DragManager";
import { CompileScript } from "../../util/Scripting";
import { Transform } from "../../util/Transform";
-import { MAX_ROW_HEIGHT } from '../globalCssVariables.scss';
+import { MAX_ROW_HEIGHT, COLLECTION_BORDER_WIDTH } from '../globalCssVariables.scss';
import '../DocumentDecorations.scss';
import { EditableView } from "../EditableView";
import { FieldView, FieldViewProps } from "../nodes/FieldView";
import "./CollectionSchemaView.scss";
-import { CollectionView } from "./CollectionView";
-import { NumCast, StrCast, BoolCast, FieldValue, Cast } from "../../../fields/Types";
+import { CollectionView, Flyout } from "./CollectionView";
+import { NumCast, StrCast, BoolCast, FieldValue, Cast, DateCast } from "../../../fields/Types";
import { Docs } from "../../documents/Documents";
import { library } from '@fortawesome/fontawesome-svg-core';
import { faExpand } from '@fortawesome/free-solid-svg-icons';
@@ -24,6 +24,15 @@ import { SchemaHeaderField } from "../../../fields/SchemaHeaderField";
import { undoBatch } from "../../util/UndoManager";
import { SnappingManager } from "../../util/SnappingManager";
import { ComputedField } from "../../../fields/ScriptField";
+import { ImageField } from "../../../fields/URLField";
+import { List } from "../../../fields/List";
+import { OverlayView } from "../OverlayView";
+import { DocumentIconContainer } from "../nodes/DocumentIcon";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import DatePicker from "react-datepicker";
+import "react-datepicker/dist/react-datepicker.css";
+import { DateField } from "../../../fields/DateField";
+const path = require('path');
library.add(faExpand);
@@ -47,6 +56,7 @@ export interface CellProps {
setPreviewDoc: (doc: Doc) => void;
setComputed: (script: string, doc: Doc, field: string, row: number, col: number) => boolean;
getField: (row: number, col?: number) => void;
+ showDoc: (doc: Doc | undefined, dataDoc?: any, screenX?: number, screenY?: number) => void;
}
@observer
@@ -54,7 +64,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
@observable protected _isEditing: boolean = false;
protected _focusRef = React.createRef<HTMLDivElement>();
protected _document = this.props.rowProps.original;
- private _dropDisposer?: DragManager.DragDropDisposer;
+ protected _dropDisposer?: DragManager.DragDropDisposer;
componentDidMount() {
document.addEventListener("keydown", this.onKeyDown);
@@ -84,6 +94,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
@action
onPointerDown = async (e: React.PointerEvent): Promise<void> => {
+
this.props.changeFocusedCellByIndex(this.props.row, this.props.col);
this.props.setPreviewDoc(this.props.rowProps.original);
@@ -129,7 +140,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
}
}
- private dropRef = (ele: HTMLElement | null) => {
+ protected dropRef = (ele: HTMLElement | null) => {
this._dropDisposer && this._dropDisposer();
if (ele) {
this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
@@ -206,6 +217,18 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
const doc = FieldValue(Cast(field, Doc));
contents = typeof field === "object" ? doc ? StrCast(doc.title) === "" ? "--" : StrCast(doc.title) : `--${typeof field}--` : `--${typeof field}--`;
}
+ if (type === "image") {
+ const image = FieldValue(Cast(field, ImageField));
+ const doc = FieldValue(Cast(field, Doc));
+ contents = typeof field === "object" ? doc ? StrCast(doc.title) === "" ? "--" : StrCast(doc.title) : `--${typeof field}--` : `--${typeof field}--`;
+ }
+ if (type === "list") {
+ contents = typeof field === "object" ? doc ? StrCast(field) === "" ? "--" : StrCast(field) : `--${typeof field}--` : `--${typeof field}--`;
+ }
+ if (type === "date") {
+ contents = typeof field === "object" ? doc ? StrCast(field) === "" ? "--" : StrCast(field) : `--${typeof field}--` : `--${typeof field}--`;
+ }
+
let className = "collectionSchemaView-cellWrapper";
if (this._isEditing) className += " editing";
@@ -220,40 +243,60 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
// );
trace();
+
+
return (
<div className="collectionSchemaView-cellContainer" style={{ cursor: fieldIsDoc ? "grab" : "auto" }} ref={dragRef} onPointerDown={this.onPointerDown} onPointerEnter={onPointerEnter} onPointerLeave={onPointerLeave}>
<div className={className} ref={this._focusRef} onPointerDown={onItemDown} tabIndex={-1}>
<div className="collectionSchemaView-cellContents" ref={type === undefined || type === "document" ? this.dropRef : null} key={props.Document[Id]}>
+
+
<EditableView
editing={this._isEditing}
isEditingCallback={this.isEditingCallback}
display={"inline"}
- contents={contents}
+ contents={contents ? contents : type === "number" ? "0" : "undefined"}
+ //contents={StrCast(contents)}
height={"auto"}
maxHeight={Number(MAX_ROW_HEIGHT)}
+ placeholder={"enter value"}
GetValue={() => {
- const cfield = ComputedField.WithoutComputed(() => FieldValue(props.Document[props.fieldKey]));
- const cscript = cfield instanceof ComputedField ? cfield.script.originalScript : undefined;
- const cfinalScript = cscript?.split("return")[cscript.split("return").length - 1];
- const val = cscript !== undefined ? `:=${cfinalScript?.substring(0, cfinalScript.length - 2)}` :
- Field.IsField(cfield) ? Field.toScriptString(cfield) : "";
- return val;
+ if (type === "number" && (contents === 0 || contents === "0")) {
+ return "0";
+ } else {
+ const cfield = ComputedField.WithoutComputed(() => FieldValue(props.Document[props.fieldKey]));
+ console.log(cfield);
+ if (type === "number") {
+ return StrCast(cfield);
+ }
+ const cscript = cfield instanceof ComputedField ? cfield.script.originalScript : undefined;
+ const cfinalScript = cscript?.split("return")[cscript.split("return").length - 1];
+ const val = cscript !== undefined ? (cfinalScript?.endsWith(";") ? `:=${cfinalScript?.substring(0, cfinalScript.length - 2)}` : cfinalScript) :
+ Field.IsField(cfield) ? Field.toScriptString(cfield) : "";
+ return val;
+ }
+
}}
SetValue={action((value: string) => {
let retVal = false;
+
if (value.startsWith(":=")) {
retVal = this.props.setComputed(value.substring(2), props.Document, this.props.rowProps.column.id!, this.props.row, this.props.col);
} else {
const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
if (script.compiled) {
retVal = this.applyToDoc(props.Document, this.props.row, this.props.col, script.run);
+ console.log("compiled");
}
+
}
if (retVal) {
this._isEditing = false; // need to set this here. otherwise, the assignment of the field will invalidate & cause render() to be called with the wrong value for 'editing'
this.props.setIsEditing(false);
}
return retVal;
+
+ //return true;
})}
OnFillDown={async (value: string) => {
const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
@@ -265,6 +308,8 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
}
}}
/>
+
+
</div >
{/* {fieldIsDoc ? docExpander : null} */}
</div>
@@ -299,12 +344,473 @@ export class CollectionSchemaStringCell extends CollectionSchemaCell {
}
@observer
+export class CollectionSchemaDateCell extends CollectionSchemaCell {
+ @observable private _date: Date = this.props.rowProps.original[this.props.rowProps.column.id as string] instanceof DateField ? DateCast(this.props.rowProps.original[this.props.rowProps.column.id as string]).date :
+ this.props.rowProps.original[this.props.rowProps.column.id as string] instanceof Date ? this.props.rowProps.original[this.props.rowProps.column.id as string] : new Date();
+
+ @action
+ handleChange = (date: any) => {
+ console.log(date);
+ this._date = date;
+ // const script = CompileScript(date.toString(), { requiredType: "Date", addReturn: true, params: { this: Doc.name } });
+ // if (script.compiled) {
+ // console.log("scripting");
+ // this.applyToDoc(this._document, this.props.row, this.props.col, script.run);
+ // } else {
+ console.log(DateCast(date));
+ // ^ DateCast is always undefined for some reason, but that is what the field should be set to
+ this._document[this.props.rowProps.column.id as string] = date as Date;
+ console.log(this._document[this.props.rowProps.column.id as string]);
+ //}
+ }
+
+ render() {
+ return <DatePicker
+ selected={this._date}
+ onSelect={date => this.handleChange(date)}
+ onChange={date => this.handleChange(date)}
+ />;
+ }
+}
+
+@observer
export class CollectionSchemaDocCell extends CollectionSchemaCell {
+
+ _overlayDisposer?: () => void;
+
+ private prop: FieldViewProps = {
+ Document: this.props.rowProps.original,
+ DataDoc: this.props.rowProps.original,
+ LibraryPath: [],
+ dropAction: "alias",
+ bringToFront: emptyFunction,
+ rootSelected: returnFalse,
+ fieldKey: this.props.rowProps.column.id as string,
+ ContainingCollectionView: this.props.CollectionView,
+ ContainingCollectionDoc: this.props.CollectionView && this.props.CollectionView.props.Document,
+ isSelected: returnFalse,
+ select: emptyFunction,
+ renderDepth: this.props.renderDepth + 1,
+ ScreenToLocalTransform: Transform.Identity,
+ focus: emptyFunction,
+ active: returnFalse,
+ whenActiveChanged: emptyFunction,
+ PanelHeight: returnZero,
+ PanelWidth: returnZero,
+ NativeHeight: returnZero,
+ NativeWidth: returnZero,
+ addDocTab: this.props.addDocTab,
+ pinToPres: this.props.pinToPres,
+ ContentScaling: returnOne,
+ docFilters: returnEmptyFilter
+ };
+ @observable private _field = this.prop.Document[this.prop.fieldKey];
+ @observable private _doc = FieldValue(Cast(this._field, Doc));
+ @observable private _docTitle = this._doc?.title;
+ @observable private _preview = false;
+ @computed get previewWidth() { return () => NumCast(this.props.Document.schemaPreviewWidth); }
+ @computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); }
+ @computed get tableWidth() { return this.prop.PanelWidth() - 2 * this.borderWidth - 4 - this.previewWidth(); }
+
+ @action
+ onSetValue = (value: string) => {
+ this._docTitle = value;
+ //this.prop.Document[this.prop.fieldKey] = this._text;
+
+ const script = CompileScript(value, {
+ addReturn: true,
+ typecheck: false,
+ transformer: DocumentIconContainer.getTransformer()
+ });
+
+ const results = script.compiled && script.run();
+ if (results && results.success) {
+
+ console.log(results.result);
+ this._doc = results.result;
+ this._document[this.prop.fieldKey] = results.result;
+ this._docTitle = this._doc?.title;
+
+ return true;
+ }
+ return false;
+ }
+
+ onFocus = () => {
+ this._overlayDisposer?.();
+ this._overlayDisposer = OverlayView.Instance.addElement(<DocumentIconContainer />, { x: 0, y: 0 });
+ }
+
+ @action
+ onOpenClick = () => {
+ this._preview = false;
+ if (this._doc) {
+ this.props.addDocTab(this._doc, "onRight");
+ return true;
+ }
+ return false;
+ }
+
+ @action
+ showPreview = (bool: boolean, e: any) => {
+ if (this._isEditing) {
+ this._preview = false;
+ } else {
+ if (bool) {
+ console.log("show doc");
+ this.props.showDoc(this._doc, this.prop.DataDoc, e.clientX, e.clientY);
+ } else {
+ console.log("no doc");
+ this.props.showDoc(undefined);
+ }
+ }
+ }
+
+ @action
+ isEditingCalling = (isEditing: boolean): void => {
+ this.showPreview(false, "");
+ document.removeEventListener("keydown", this.onKeyDown);
+ isEditing && document.addEventListener("keydown", this.onKeyDown);
+ this._isEditing = isEditing;
+ this.props.setIsEditing(isEditing);
+ this.props.changeFocusedCellByIndex(this.props.row, this.props.col);
+ }
+
+ onDown = (e: any) => {
+ this.props.changeFocusedCellByIndex(this.props.row, this.props.col);
+ this.props.setPreviewDoc(this.props.rowProps.original);
+
+ let url: string;
+ if (url = StrCast(this.props.rowProps.row.href)) {
+ try {
+ new URL(url);
+ const temp = window.open(url)!;
+ temp.blur();
+ window.focus();
+ } catch { }
+ }
+
+ const field = this.props.rowProps.original[this.props.rowProps.column.id!];
+ const doc = FieldValue(Cast(field, Doc));
+ if (typeof field === "object" && doc) this.props.setPreviewDoc(doc);
+
+ this.showPreview(true, e);
+
+ }
+
render() {
- return this.renderCellWithType("document");
+ if (typeof this._field === "object" && this._doc && this._docTitle) {
+ return (
+ <div className="collectionSchemaView-cellWrapper" ref={this._focusRef} tabIndex={-1}
+ onPointerDown={(e) => { this.onDown(e); }}
+ onPointerEnter={(e) => { this.showPreview(true, e); }}
+ onPointerLeave={(e) => { this.showPreview(false, e); }}
+ >
+
+ <div className="collectionSchemaView-cellContents-document"
+ style={{ padding: "5.9px" }}
+ ref={this.dropRef}
+ onFocus={this.onFocus}
+ onBlur={() => this._overlayDisposer?.()}
+ >
+
+ <EditableView
+ editing={this._isEditing}
+ isEditingCallback={this.isEditingCalling}
+ display={"inline"}
+ contents={this._docTitle}
+ height={"auto"}
+ maxHeight={Number(MAX_ROW_HEIGHT)}
+ GetValue={() => {
+ return StrCast(this._docTitle);
+ }}
+ SetValue={action((value: string) => {
+ this.onSetValue(value);
+ this.showPreview(false, "");
+ return true;
+ })}
+ />
+ </div >
+ <div onClick={this.onOpenClick} className="collectionSchemaView-cellContents-docButton">
+ <FontAwesomeIcon icon="external-link-alt" size="lg" ></FontAwesomeIcon> </div>
+ </div>
+ );
+ } else {
+ return this.renderCellWithType("document");
+ }
+ }
+}
+
+@observer
+export class CollectionSchemaImageCell extends CollectionSchemaCell {
+ // render() {
+ // return this.renderCellWithType("image");
+ // }
+
+ choosePath(url: URL, dataDoc: any) {
+ const lower = url.href.toLowerCase();
+ if (url.protocol === "data") {
+ return url.href;
+ } else if (url.href.indexOf(window.location.origin) === -1) {
+ return Utils.CorsProxy(url.href);
+ } else if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower)) {
+ return url.href;//Why is this here
+ }
+ const ext = path.extname(url.href);
+ const _curSuffix = "_o";
+ return url.href.replace(ext, _curSuffix + ext);
+ }
+
+ render() {
+ const props: FieldViewProps = {
+ Document: this.props.rowProps.original,
+ DataDoc: this.props.rowProps.original,
+ LibraryPath: [],
+ dropAction: "alias",
+ bringToFront: emptyFunction,
+ rootSelected: returnFalse,
+ fieldKey: this.props.rowProps.column.id as string,
+ ContainingCollectionView: this.props.CollectionView,
+ ContainingCollectionDoc: this.props.CollectionView && this.props.CollectionView.props.Document,
+ isSelected: returnFalse,
+ select: emptyFunction,
+ renderDepth: this.props.renderDepth + 1,
+ ScreenToLocalTransform: Transform.Identity,
+ focus: emptyFunction,
+ active: returnFalse,
+ whenActiveChanged: emptyFunction,
+ PanelHeight: returnZero,
+ PanelWidth: returnZero,
+ NativeHeight: returnZero,
+ NativeWidth: returnZero,
+ addDocTab: this.props.addDocTab,
+ pinToPres: this.props.pinToPres,
+ ContentScaling: returnOne,
+ docFilters: returnEmptyFilter
+ };
+
+ let image = true;
+ let url = [];
+ if (props.DataDoc) {
+ const field = Cast(props.DataDoc[props.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
+ const alts = DocListCast(props.DataDoc[props.fieldKey + "-alternates"]); // retrieve alternate documents that may be rendered as alternate images
+ const altpaths = alts.map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url).filter(url => url).map(url => this.choosePath(url, props.DataDoc)); // access the primary layout data of the alternate documents
+ const paths = field ? [this.choosePath(field.url, props.DataDoc), ...altpaths] : altpaths;
+ if (paths.length) {
+ url = paths;
+ } else {
+ url = [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")];
+ image = false;
+ }
+ //url = paths.length ? paths : [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")];
+ } else {
+ url = [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")];
+ image = false;
+ }
+
+ const heightToWidth = NumCast(props.DataDoc?._nativeHeight) / NumCast(props.DataDoc?._nativeWidth);
+ const height = this.props.rowProps.width * heightToWidth;
+
+ if (props.fieldKey === "data") {
+ if (url !== []) {
+ const reference = React.createRef<HTMLDivElement>();
+ return (
+ <div className="collectionSchemaView-cellWrapper" ref={this._focusRef} tabIndex={-1} onPointerDown={this.onPointerDown}>
+ <div className="collectionSchemaView-cellContents" key={this._document[Id]} ref={reference}>
+ <img src={url[0]} width={image ? this.props.rowProps.width : "30px"}
+ height={image ? height : "30px"} />
+ </div >
+ </div>
+ );
+
+ } else {
+ return this.renderCellWithType("image");
+ }
+ } else {
+ return this.renderCellWithType("image");
+ }
}
}
+
+
+
+
+@observer
+export class CollectionSchemaListCell extends CollectionSchemaCell {
+
+ _overlayDisposer?: () => void;
+
+ private prop: FieldViewProps = {
+ Document: this.props.rowProps.original,
+ DataDoc: this.props.rowProps.original,
+ LibraryPath: [],
+ dropAction: "alias",
+ bringToFront: emptyFunction,
+ rootSelected: returnFalse,
+ fieldKey: this.props.rowProps.column.id as string,
+ ContainingCollectionView: this.props.CollectionView,
+ ContainingCollectionDoc: this.props.CollectionView && this.props.CollectionView.props.Document,
+ isSelected: returnFalse,
+ select: emptyFunction,
+ renderDepth: this.props.renderDepth + 1,
+ ScreenToLocalTransform: Transform.Identity,
+ focus: emptyFunction,
+ active: returnFalse,
+ whenActiveChanged: emptyFunction,
+ PanelHeight: returnZero,
+ PanelWidth: returnZero,
+ NativeHeight: returnZero,
+ NativeWidth: returnZero,
+ addDocTab: this.props.addDocTab,
+ pinToPres: this.props.pinToPres,
+ ContentScaling: returnOne,
+ docFilters: returnEmptyFilter
+ };
+ @observable private _field = this.prop.Document[this.prop.fieldKey];
+ @observable private _optionsList = this._field as List<any>;
+ @observable private _opened = false;
+ @observable private _text = "select an item";
+ @observable private _selectedNum = 0;
+
+ @action
+ toggleOpened(open: boolean) {
+ console.log("open: " + open);
+ this._opened = open;
+ }
+
+ // @action
+ // onChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
+ // this._text = e.target.value;
+
+ // // change if its a document
+ // this._optionsList[this._selectedNum] = this._text;
+ // }
+
+ @action
+ onSetValue = (value: string) => {
+
+
+ this._text = value;
+
+ // change if its a document
+ this._optionsList[this._selectedNum] = this._text;
+
+ (this.prop.Document[this.prop.fieldKey] as List<any>).splice(this._selectedNum, 1, value);
+
+ }
+
+ @action
+ onSelected = (element: string, index: number) => {
+ this._text = element;
+ this._selectedNum = index;
+ }
+
+ onFocus = () => {
+ this._overlayDisposer?.();
+ this._overlayDisposer = OverlayView.Instance.addElement(<DocumentIconContainer />, { x: 0, y: 0 });
+ }
+
+
+ render() {
+
+ const dragRef: React.RefObject<HTMLDivElement> = React.createRef();
+
+ let type = "list";
+
+ let link = false;
+ let doc = false;
+ const reference = React.createRef<HTMLDivElement>();
+
+ if (typeof this._field === "object" && this._optionsList[0]) {
+
+ const options = this._optionsList.map((element, index) => {
+
+ if (element instanceof Doc) {
+ doc = true;
+ type = "document";
+ if (this.prop.fieldKey.toLowerCase() === "links") {
+ link = true;
+ type = "link";
+ }
+ const document = FieldValue(Cast(element, Doc));
+ const title = element.title;
+ return <div
+ className="collectionSchemaView-dropdownOption"
+ onPointerDown={(e) => { this.onSelected(StrCast(element.title), index); }}
+ style={{ padding: "6px" }}>
+ {title}
+ </div>;
+
+ } else {
+ return <div
+ className="collectionSchemaView-dropdownOption"
+ onPointerDown={(e) => { this.onSelected(StrCast(element), index); }}
+ style={{ padding: "6px" }}>{element}</div>;
+ }
+ });
+
+ const plainText = <div style={{ padding: "5.9px" }}>{this._text}</div>;
+ // const textarea = <textarea onChange={this.onChange} value={this._text}
+ // onFocus={doc ? this.onFocus : undefined}
+ // onBlur={doc ? e => this._overlayDisposer?.() : undefined}
+ // style={{ resize: "none" }}
+ // placeholder={"select an item"}></textarea>;
+
+ const textarea = <div className="collectionSchemaView-cellContents"
+ style={{ padding: "5.9px" }}
+ ref={type === undefined || type === "document" ? this.dropRef : null} key={this.prop.Document[Id]}>
+ <EditableView
+ editing={this._isEditing}
+ isEditingCallback={this.isEditingCallback}
+ display={"inline"}
+ contents={this._text}
+ height={"auto"}
+ maxHeight={Number(MAX_ROW_HEIGHT)}
+ GetValue={() => {
+ return this._text;
+ }}
+ SetValue={action((value: string) => {
+
+ // add special for params
+ this.onSetValue(value);
+ return true;
+ })}
+ />
+ </div >;
+
+ //☰
+
+ const dropdown = <div>
+ {options}
+ </div>;
+
+ return (
+ <div className="collectionSchemaView-cellWrapper" ref={this._focusRef} tabIndex={-1} onPointerDown={this.onPointerDown}>
+ <div className="collectionSchemaView-cellContents" key={this._document[Id]} ref={reference}>
+ <div className="collectionSchemaView-dropDownWrapper">
+ <button type="button" className="collectionSchemaView-dropdownButton" onClick={(e) => { this.toggleOpened(!this._opened); }}
+ style={{ right: "length", position: "relative" }}>
+ {this._opened ? <FontAwesomeIcon icon="caret-up" size="lg" ></FontAwesomeIcon>
+ : <FontAwesomeIcon icon="caret-down" size="lg" ></FontAwesomeIcon>}
+ </button>
+ <div className="collectionSchemaView-dropdownText"> {link ? plainText : textarea} </div>
+ </div>
+
+ {this._opened ? dropdown : null}
+ </div >
+ </div>
+ );
+ } else {
+ return this.renderCellWithType("list");
+ }
+ }
+}
+
+
+
+
+
@observer
export class CollectionSchemaCheckboxCell extends CollectionSchemaCell {
@observable private _isChecked: boolean = typeof this.props.rowProps.original[this.props.rowProps.column.id as string] === "boolean" ? BoolCast(this.props.rowProps.original[this.props.rowProps.column.id as string]) : false;
diff --git a/src/client/views/collections/CollectionSchemaHeaders.tsx b/src/client/views/collections/CollectionSchemaHeaders.tsx
index dae0600b1..efff4db98 100644
--- a/src/client/views/collections/CollectionSchemaHeaders.tsx
+++ b/src/client/views/collections/CollectionSchemaHeaders.tsx
@@ -2,7 +2,7 @@ import React = require("react");
import { action, observable } from "mobx";
import { observer } from "mobx-react";
import "./CollectionSchemaView.scss";
-import { faPlus, faFont, faHashtag, faAlignJustify, faCheckSquare, faToggleOn, faSortAmountDown, faSortAmountUp, faTimes } from '@fortawesome/free-solid-svg-icons';
+import { faPlus, faFont, faHashtag, faAlignJustify, faCheckSquare, faToggleOn, faSortAmountDown, faSortAmountUp, faTimes, faImage, faListUl, faCalendar } from '@fortawesome/free-solid-svg-icons';
import { library, IconProp } from "@fortawesome/fontawesome-svg-core";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { ColumnType } from "./CollectionSchemaView";
@@ -13,7 +13,7 @@ const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
-library.add(faPlus, faFont, faHashtag, faAlignJustify, faCheckSquare, faToggleOn, faFile as any, faSortAmountDown, faSortAmountUp, faTimes);
+library.add(faPlus, faFont, faHashtag, faAlignJustify, faCheckSquare, faToggleOn, faFile as any, faSortAmountDown, faSortAmountUp, faTimes, faImage, faListUl, faCalendar);
export interface HeaderProps {
keyValue: SchemaHeaderField;
@@ -33,7 +33,9 @@ export interface HeaderProps {
export class CollectionSchemaHeader extends React.Component<HeaderProps> {
render() {
const icon: IconProp = this.props.keyType === ColumnType.Number ? "hashtag" : this.props.keyType === ColumnType.String ? "font" :
- this.props.keyType === ColumnType.Boolean ? "check-square" : this.props.keyType === ColumnType.Doc ? "file" : "align-justify";
+ this.props.keyType === ColumnType.Boolean ? "check-square" : this.props.keyType === ColumnType.Doc ? "file" :
+ this.props.keyType === ColumnType.Image ? "image" : this.props.keyType === ColumnType.List ? "list-ul" : this.props.keyType === ColumnType.Date ? "calendar" :
+ "align-justify";
return (
<div className="collectionSchemaView-header" style={{ background: this.props.keyValue.color }}>
<CollectionSchemaColumnMenu
@@ -72,6 +74,16 @@ export class CollectionSchemaAddColumnHeader extends React.Component<AddColumnHe
}
}
+
+
+
+
+
+
+
+
+
+
export interface ColumnMenuProps {
columnField: SchemaHeaderField;
// keyValue: string;
@@ -160,10 +172,22 @@ export class CollectionSchemaColumnMenu extends React.Component<ColumnMenuProps>
<FontAwesomeIcon icon={"check-square"} size="sm" />
Checkbox
</div>
+ <div className={"columnMenu-option" + (type === ColumnType.List ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.List)}>
+ <FontAwesomeIcon icon={"list-ul"} size="sm" />
+ List
+ </div>
<div className={"columnMenu-option" + (type === ColumnType.Doc ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Doc)}>
<FontAwesomeIcon icon={"file"} size="sm" />
Document
</div>
+ <div className={"columnMenu-option" + (type === ColumnType.Image ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Image)}>
+ <FontAwesomeIcon icon={"image"} size="sm" />
+ Image
+ </div>
+ <div className={"columnMenu-option" + (type === ColumnType.Date ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Date)}>
+ <FontAwesomeIcon icon={"calendar"} size="sm" />
+ Date
+ </div>
</div>
</div >
);
@@ -258,7 +282,7 @@ export class CollectionSchemaColumnMenu extends React.Component<ColumnMenuProps>
}
-interface KeysDropdownProps {
+export interface KeysDropdownProps {
keyValue: string;
possibleKeys: string[];
existingKeys: string[];
@@ -266,9 +290,10 @@ interface KeysDropdownProps {
addNew: boolean;
onSelect: (oldKey: string, newKey: string, addnew: boolean) => void;
setIsEditing: (isEditing: boolean) => void;
+ width?: string;
}
@observer
-class KeysDropdown extends React.Component<KeysDropdownProps> {
+export class KeysDropdown extends React.Component<KeysDropdownProps> {
@observable private _key: string = this.props.keyValue;
@observable private _searchTerm: string = this.props.keyValue;
@observable private _isOpen: boolean = false;
@@ -331,17 +356,26 @@ class KeysDropdown extends React.Component<KeysDropdownProps> {
renderOptions = (): JSX.Element[] | JSX.Element => {
if (!this._isOpen) return <></>;
- const keyOptions = this._searchTerm === "" ? this.props.possibleKeys : this.props.possibleKeys.filter(key => key.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1);
+ const searchTerm = this._searchTerm.trim() === "New field" ? "" : this._searchTerm;
+
+ const keyOptions = searchTerm === "" ? this.props.possibleKeys : this.props.possibleKeys.filter(key => key.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1);
const exactFound = keyOptions.findIndex(key => key.toUpperCase() === this._searchTerm.toUpperCase()) > -1 ||
this.props.existingKeys.findIndex(key => key.toUpperCase() === this._searchTerm.toUpperCase()) > -1;
const options = keyOptions.map(key => {
- return <div key={key} className="key-option" onPointerDown={e => e.stopPropagation()} onClick={() => { this.onSelect(key); this.setSearchTerm(""); }}>{key}</div>;
+ return <div key={key} className="key-option" style={{
+ border: "1px solid lightgray",
+ width: this.props.width, maxWidth: this.props.width, overflowX: "hidden"
+ }}
+ onPointerDown={e => e.stopPropagation()} onClick={() => { this.onSelect(key); this.setSearchTerm(""); }}>{key}</div>;
});
// if search term does not already exist as a group type, give option to create new group type
if (!exactFound && this._searchTerm !== "" && this.props.canAddNew) {
- options.push(<div key={""} className="key-option"
+ options.push(<div key={""} className="key-option" style={{
+ border: "1px solid lightgray",
+ width: this.props.width, maxWidth: this.props.width, overflowX: "hidden"
+ }}
onClick={() => { this.onSelect(this._searchTerm); this.setSearchTerm(""); }}>
Create "{this._searchTerm}" key</div>);
}
@@ -351,10 +385,19 @@ class KeysDropdown extends React.Component<KeysDropdownProps> {
render() {
return (
- <div className="keys-dropdown">
- <input className="keys-search" ref={this._inputRef} type="text" value={this._searchTerm} placeholder="Column key" onKeyDown={this.onKeyDown}
- onChange={e => this.onChange(e.target.value)} onFocus={this.onFocus} onBlur={this.onBlur}></input>
- <div className="keys-options-wrapper" onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerOut}>
+ <div className="keys-dropdown" style={{ width: this.props.width, maxWidth: this.props.width, overflowX: "hidden" }}>
+ <input className="keys-search" //style={{ width: this.props.width, maxWidth: "1000" }}
+ ref={this._inputRef} type="text" value={this._searchTerm} placeholder="Column key" onKeyDown={this.onKeyDown}
+ onChange={e => this.onChange(e.target.value)}
+ onClick={(e) => {
+ //this._inputRef.current!.select();
+ e.stopPropagation();
+ }} onFocus={this.onFocus} onBlur={this.onBlur}></input>
+ <div className="keys-options-wrapper" style={{
+ backgroundColor: "white",
+ width: this.props.width, maxWidth: this.props.width, overflowX: "hidden"
+ }}
+ onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerOut}>
{this.renderOptions()}
</div>
</div >
diff --git a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
index b206765e8..b77173b25 100644
--- a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
+++ b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
@@ -137,6 +137,7 @@ export interface MovableRowProps {
textWrapRow: (doc: Doc) => void;
rowWrapped: boolean;
dropAction: string;
+ addDocTab: any;
}
export class MovableRow extends React.Component<MovableRowProps> {
@@ -232,6 +233,7 @@ export class MovableRow extends React.Component<MovableRowProps> {
<div className="row-dragger">
<div className="row-option" onClick={undoBatch(() => this.props.removeDoc(this.props.rowInfo.original))}><FontAwesomeIcon icon="trash" size="sm" /></div>
<div className="row-option" style={{ cursor: "grab" }} ref={reference} onPointerDown={onItemDown}><FontAwesomeIcon icon="grip-vertical" size="sm" /></div>
+ <div className="row-option" onClick={() => this.props.addDocTab(this.props.rowInfo.original, "onRight")}><FontAwesomeIcon icon="external-link-alt" size="sm" /></div>
</div>
{children}
</ReactTableDefaults.TrComponent>
diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss
index a24140b48..5226a60f1 100644
--- a/src/client/views/collections/CollectionSchemaView.scss
+++ b/src/client/views/collections/CollectionSchemaView.scss
@@ -62,10 +62,15 @@
width: calc(100% - 52px);
margin-left: 50px;
+ z-index: 100;
+ overflow-y: visible;
+
&.-header {
font-size: 12px;
height: 30px;
box-shadow: none;
+ z-index: 100;
+ overflow-y: visible;
}
.rt-resizable-header-content {
@@ -172,27 +177,36 @@
}
-.collectionSchemaView-header {
+.collectionSchema-header-menu {
height: 100%;
- color: gray;
+ z-index: 100;
+ position: absolute;
+ background:white;
- .collectionSchema-header-menu {
+ .collectionSchema-header-toggler {
+ z-index: 100;
+ width: 100%;
height: 100%;
+ padding: 4px;
+ letter-spacing: 2px;
+ text-transform: uppercase;
- .collectionSchema-header-toggler {
- width: 100%;
- height: 100%;
- padding: 4px;
- letter-spacing: 2px;
- text-transform: uppercase;
-
- svg {
- margin-right: 4px;
- }
+ svg {
+ margin-right: 4px;
}
}
}
+.collectionSchemaView-header {
+ height: 100%;
+ color: gray;
+ z-index: 100;
+ overflow-y: visible;
+ display: flex;
+ justify-content: space-between;
+ flex-wrap: wrap;
+}
+
button.add-column {
width: 28px;
}
@@ -253,13 +267,16 @@ button.add-column {
.keys-dropdown {
position: relative;
- width: 100%;
+ //width: 100%;
+ background-color: white;
input {
border: 2px solid $light-color-secondary;
padding: 3px;
height: 28px;
font-weight: bold;
+ letter-spacing: "2px";
+ text-transform: "uppercase";
&:focus {
font-weight: normal;
@@ -273,11 +290,14 @@ button.add-column {
position: absolute;
top: 28px;
box-shadow: 0 10px 16px rgba(0, 0, 0, 0.1);
+ background-color: white;
.key-option {
- background-color: $light-color;
+ //background-color: $light-color;
+ background-color: white;
border: 1px solid lightgray;
padding: 2px 3px;
+ overflow-x: hidden;
&:not(:first-child) {
border-top: 0;
@@ -412,6 +432,56 @@ button.add-column {
&:hover .collectionSchemaView-cellContents-docExpander {
display: block;
}
+
+
+ .collectionSchemaView-cellContents-document {
+ display: inline-block;
+ }
+
+ .collectionSchemaView-cellContents-docButton {
+ float: right;
+ width: "15px";
+ height: "15px";
+ }
+
+ .collectionSchemaView-dropdownWrapper {
+
+ border: grey;
+ border-style: solid;
+ border-width: 1px;
+ height: 100%;
+
+ .collectionSchemaView-dropdownButton {
+
+ //display: inline-block;
+ float: left;
+ height: 100%;
+
+
+ }
+
+ .collectionSchemaView-dropdownText {
+ display: inline-block;
+ //float: right;
+ height: 100%;
+ display: "flex";
+ font-size: 13;
+ justify-content: "center";
+ align-items: "center";
+ }
+
+ }
+
+ .collectionSchemaView-dropdownContainer {
+ position: absolute;
+ border: 1px solid rgba(0, 0, 0, 0.04);
+ box-shadow: 0 16px 24px 2px rgba(0, 0, 0, 0.14);
+
+ .collectionSchemaView-dropdownOption:hover {
+ background-color: rgba(0, 0, 0, 0.14);
+ cursor: pointer;
+ }
+ }
}
.collectionSchemaView-cellContents-docExpander {
@@ -422,6 +492,7 @@ button.add-column {
top: 0;
right: 0;
background-color: lightgray;
+
}
.doc-drag-over {
@@ -429,6 +500,10 @@ button.add-column {
}
.collectionSchemaView-toolbar {
+ z-index: 100;
+}
+
+.collectionSchemaView-toolbar {
height: 30px;
display: flex;
justify-content: flex-end;
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index b780da97e..3c42a2f1c 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -4,31 +4,27 @@ import { faCog, faPlus, faSortDown, faSortUp, faTable } from '@fortawesome/free-
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, observable, untracked } from "mobx";
import { observer } from "mobx-react";
-import ReactTable, { CellInfo, Column, ComponentPropsGetterR, Resize, SortingRule } from "react-table";
+import { Resize } from "react-table";
import "react-table/react-table.css";
-import { Doc, DocListCast, Field, Opt } from "../../../fields/Doc";
-import { Id } from "../../../fields/FieldSymbols";
+import { Doc } from "../../../fields/Doc";
import { List } from "../../../fields/List";
import { listSpec } from "../../../fields/Schema";
-import { SchemaHeaderField } from "../../../fields/SchemaHeaderField";
-import { ComputedField } from "../../../fields/ScriptField";
-import { Cast, FieldValue, NumCast, StrCast, BoolCast } from "../../../fields/Types";
+import { SchemaHeaderField, PastelSchemaPalette } from "../../../fields/SchemaHeaderField";
+import { Cast, NumCast, StrCast } from "../../../fields/Types";
import { Docs, DocumentOptions } from "../../documents/Documents";
-import { CompileScript, Transformer, ts } from "../../util/Scripting";
import { Transform } from "../../util/Transform";
import { undoBatch } from "../../util/UndoManager";
import { COLLECTION_BORDER_WIDTH } from '../../views/globalCssVariables.scss';
-import { ContextMenu } from "../ContextMenu";
import '../DocumentDecorations.scss';
-import { CellProps, CollectionSchemaCell, CollectionSchemaCheckboxCell, CollectionSchemaDocCell, CollectionSchemaNumberCell, CollectionSchemaStringCell } from "./CollectionSchemaCells";
-import { CollectionSchemaAddColumnHeader, CollectionSchemaHeader } from "./CollectionSchemaHeaders";
-import { MovableColumn, MovableRow } from "./CollectionSchemaMovableTableHOC";
+import { KeysDropdown } from "./CollectionSchemaHeaders";
import "./CollectionSchemaView.scss";
import { CollectionSubView } from "./CollectionSubView";
-import { CollectionView } from "./CollectionView";
import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView";
import { setupMoveUpEvents, emptyFunction, returnZero, returnOne, returnFalse } from "../../../Utils";
import { SnappingManager } from "../../util/SnappingManager";
+import Measure from "react-measure";
+import { SchemaTable } from "./SchemaTable";
+import { TraceMobx } from "../../../fields/util";
library.add(faCog, faPlus, faSortUp, faSortDown);
library.add(faTable);
@@ -40,6 +36,9 @@ export enum ColumnType {
String,
Boolean,
Doc,
+ Image,
+ List,
+ Date
}
// this map should be used for keys that should have a const type of value
const columnTypes: Map<string, ColumnType> = new Map([
@@ -62,6 +61,350 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
@computed get tableWidth() { return this.props.PanelWidth() - 2 * this.borderWidth - this.DIVIDER_WIDTH - this.previewWidth(); }
@computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); }
+ @observable _menuWidth = 0;
+ @observable _headerOpen = false;
+ @observable _isOpen = false;
+ @observable _node: HTMLDivElement | null = null;
+ @observable _headerIsEditing = false;
+ @observable _col: any = "";
+ @observable _menuHeight = 0;
+ @observable _pointerX = 0;
+ @observable _pointerY = 0;
+ @observable _openTypes: boolean = false;
+ @computed get menuCoordinates() {
+ const x = Math.max(0, Math.min(document.body.clientWidth - this._menuWidth, this._pointerX));
+ const y = Math.max(0, Math.min(document.body.clientHeight - this._menuHeight, this._pointerY));
+ return this.props.ScreenToLocalTransform().transformPoint(x, y);
+ }
+
+ @observable scale = this.props.ScreenToLocalTransform().Scale;
+
+ @computed get columns() {
+ return Cast(this.props.Document._schemaHeaders, listSpec(SchemaHeaderField), []);
+ }
+ set columns(columns: SchemaHeaderField[]) {
+ this.props.Document._schemaHeaders = new List<SchemaHeaderField>(columns);
+ }
+
+ get documentKeys() {
+ const docs = this.childDocs;
+ const keys: { [key: string]: boolean } = {};
+ // bcz: ugh. this is untracked since otherwise a large collection of documents will blast the server for all their fields.
+ // then as each document's fields come back, we update the documents _proxies. Each time we do this, the whole schema will be
+ // invalidated and re-rendered. This workaround will inquire all of the document fields before the options button is clicked.
+ // then by the time the options button is clicked, all of the fields should be in place. If a new field is added while this menu
+ // is displayed (unlikely) it won't show up until something else changes.
+ //TODO Types
+ untracked(() => docs.map(doc => Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => keys[key] = false))));
+
+ this.columns.forEach(key => keys[key.heading] = true);
+ return Array.from(Object.keys(keys));
+ }
+ @computed get possibleKeys() { return this.documentKeys.filter(key => this.columns.findIndex(existingKey => existingKey.heading.toUpperCase() === key.toUpperCase()) === -1); }
+
+
+ componentDidMount() {
+ document.addEventListener("pointerdown", this.detectClick);
+ }
+
+ componentWillUnmount() {
+ document.removeEventListener("pointerdown", this.detectClick);
+ }
+
+ @action setHeaderIsEditing = (isEditing: boolean) => this._headerIsEditing = isEditing;
+
+ detectClick = (e: PointerEvent): void => {
+ if (this._node && this._node.contains(e.target as Node)) {
+ } else {
+ this._isOpen = false;
+ this.setHeaderIsEditing(false);
+ this.closeHeader();
+ }
+ }
+
+ @action
+ toggleIsOpen = (): void => {
+ this._isOpen = !this._isOpen;
+ this.setHeaderIsEditing(this._isOpen);
+ }
+
+ @action
+ changeColumnType = (type: ColumnType, col: any): void => {
+ this._openTypes = false;
+ this.setColumnType(col, type);
+ }
+
+ changeColumnSort = (desc: boolean | undefined, col: any): void => {
+ this.setColumnSort(col, desc);
+ }
+
+ changeColumnColor = (color: string, col: any): void => {
+ this.setColumnColor(col, color);
+ }
+
+ @undoBatch
+ setColumnType = (columnField: SchemaHeaderField, type: ColumnType): void => {
+ if (columnTypes.get(columnField.heading)) return;
+
+ const columns = this.columns;
+ const index = columns.indexOf(columnField);
+ if (index > -1) {
+ columnField.setType(NumCast(type));
+ columns[index] = columnField;
+ this.columns = columns;
+ }
+ }
+
+ @undoBatch
+ setColumnColor = (columnField: SchemaHeaderField, color: string): void => {
+ const columns = this.columns;
+ const index = columns.indexOf(columnField);
+ if (index > -1) {
+ columnField.setColor(color);
+ columns[index] = columnField;
+ this.columns = columns; // need to set the columns to trigger rerender
+ }
+ }
+
+ @undoBatch
+ @action
+ setColumnSort = (columnField: SchemaHeaderField, descending: boolean | undefined) => {
+ const columns = this.columns;
+ const index = columns.findIndex(c => c.heading === columnField.heading);
+ const column = columns[index];
+ column.setDesc(descending);
+ columns[index] = column;
+ this.columns = columns;
+ }
+
+ @action
+ setNode = (node: HTMLDivElement): void => {
+ node && (this._node = node);
+ }
+
+ @action
+ typesDropdownChange = (bool: boolean) => {
+ this._openTypes = bool;
+ }
+
+ renderTypes = (col: any) => {
+ if (columnTypes.get(col.heading)) return (null);
+
+ const type = col.type;
+
+ const anyType = <div className={"columnMenu-option" + (type === ColumnType.Any ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Any, col)}>
+ <FontAwesomeIcon icon={"align-justify"} size="sm" />
+ Any
+ </div>;
+
+ const numType = <div className={"columnMenu-option" + (type === ColumnType.Number ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Number, col)}>
+ <FontAwesomeIcon icon={"hashtag"} size="sm" />
+ Number
+ </div>;
+
+ const textType = <div className={"columnMenu-option" + (type === ColumnType.String ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.String, col)}>
+ <FontAwesomeIcon icon={"font"} size="sm" />
+ Text
+ </div>;
+
+ const boolType = <div className={"columnMenu-option" + (type === ColumnType.Boolean ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Boolean, col)}>
+ <FontAwesomeIcon icon={"check-square"} size="sm" />
+ Checkbox
+ </div>;
+
+ const listType = <div className={"columnMenu-option" + (type === ColumnType.List ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.List, col)}>
+ <FontAwesomeIcon icon={"list-ul"} size="sm" />
+ List
+ </div>;
+
+ const docType = <div className={"columnMenu-option" + (type === ColumnType.Doc ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Doc, col)}>
+ <FontAwesomeIcon icon={"file"} size="sm" />
+ Document
+ </div>;
+
+ const imageType = <div className={"columnMenu-option" + (type === ColumnType.Image ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Image, col)}>
+ <FontAwesomeIcon icon={"image"} size="sm" />
+ Image
+ </div>;
+
+ const dateType = <div className={"columnMenu-option" + (type === ColumnType.Date ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Date, col)}>
+ <FontAwesomeIcon icon={"calendar"} size="sm" />
+ Date
+ </div>;
+
+
+ const allColumnTypes = <div className="columnMenu-types">
+ {anyType}
+ {numType}
+ {textType}
+ {boolType}
+ {listType}
+ {docType}
+ {imageType}
+ {dateType}
+ </div>;
+
+ const justColType = type === ColumnType.Any ? anyType : type === ColumnType.Number ? numType :
+ type === ColumnType.String ? textType : type === ColumnType.Boolean ? boolType :
+ type === ColumnType.List ? listType : type === ColumnType.Doc ? docType :
+ type === ColumnType.Date ? dateType : imageType;
+
+ return (
+ <div className="collectionSchema-headerMenu-group">
+ <div onClick={() => this.typesDropdownChange(!this._openTypes)}>
+ <label>Column type:</label>
+ <FontAwesomeIcon icon={"caret-down"} size="sm" style={{ float: "right" }} />
+ </div>
+ {this._openTypes ? allColumnTypes : justColType}
+ </div >
+ );
+ }
+
+ renderSorting = (col: any) => {
+ const sort = col.desc;
+ return (
+ <div className="collectionSchema-headerMenu-group">
+ <label>Sort by:</label>
+ <div className="columnMenu-sort">
+ <div className={"columnMenu-option" + (sort === true ? " active" : "")} onClick={() => this.changeColumnSort(true, col)}>
+ <FontAwesomeIcon icon="sort-amount-down" size="sm" />
+ Sort descending
+ </div>
+ <div className={"columnMenu-option" + (sort === false ? " active" : "")} onClick={() => this.changeColumnSort(false, col)}>
+ <FontAwesomeIcon icon="sort-amount-up" size="sm" />
+ Sort ascending
+ </div>
+ <div className="columnMenu-option" onClick={() => this.changeColumnSort(undefined, col)}>
+ <FontAwesomeIcon icon="times" size="sm" />
+ Clear sorting
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+ renderColors = (col: any) => {
+ const selected = col.color;
+
+ const pink = PastelSchemaPalette.get("pink2");
+ const purple = PastelSchemaPalette.get("purple2");
+ const blue = PastelSchemaPalette.get("bluegreen1");
+ const yellow = PastelSchemaPalette.get("yellow4");
+ const red = PastelSchemaPalette.get("red2");
+ const gray = "#f1efeb";
+
+ return (
+ <div className="collectionSchema-headerMenu-group">
+ <label>Color:</label>
+ <div className="columnMenu-colors">
+ <div className={"columnMenu-colorPicker" + (selected === pink ? " active" : "")} style={{ backgroundColor: pink }} onClick={() => this.changeColumnColor(pink!, col)}></div>
+ <div className={"columnMenu-colorPicker" + (selected === purple ? " active" : "")} style={{ backgroundColor: purple }} onClick={() => this.changeColumnColor(purple!, col)}></div>
+ <div className={"columnMenu-colorPicker" + (selected === blue ? " active" : "")} style={{ backgroundColor: blue }} onClick={() => this.changeColumnColor(blue!, col)}></div>
+ <div className={"columnMenu-colorPicker" + (selected === yellow ? " active" : "")} style={{ backgroundColor: yellow }} onClick={() => this.changeColumnColor(yellow!, col)}></div>
+ <div className={"columnMenu-colorPicker" + (selected === red ? " active" : "")} style={{ backgroundColor: red }} onClick={() => this.changeColumnColor(red!, col)}></div>
+ <div className={"columnMenu-colorPicker" + (selected === gray ? " active" : "")} style={{ backgroundColor: gray }} onClick={() => this.changeColumnColor(gray, col)}></div>
+ </div>
+ </div>
+ );
+ }
+
+ @undoBatch
+ @action
+ changeColumns = (oldKey: string, newKey: string, addNew: boolean) => {
+ const columns = this.columns;
+ if (columns === undefined) {
+ this.columns = new List<SchemaHeaderField>([new SchemaHeaderField(newKey, "f1efeb")]);
+ } else {
+ if (addNew) {
+ columns.push(new SchemaHeaderField(newKey, "f1efeb"));
+ this.columns = columns;
+ } else {
+ const index = columns.map(c => c.heading).indexOf(oldKey);
+ if (index > -1) {
+ const column = columns[index];
+ column.setHeading(newKey);
+ columns[index] = column;
+ this.columns = columns;
+ }
+ }
+ }
+ }
+
+ @action
+ openHeader = (col: any, screenx: number, screeny: number) => {
+ console.log("header opening");
+ this._col = col;
+ this._headerOpen = !this._headerOpen;
+ this._pointerX = screenx;
+ this._pointerY = screeny;
+ }
+
+ @action
+ closeHeader = () => { this._headerOpen = false; }
+
+ renderKeysDropDown = (col: any) => {
+ return <KeysDropdown
+ keyValue={col.heading}
+ possibleKeys={this.possibleKeys}
+ existingKeys={this.columns.map(c => c.heading)}
+ canAddNew={true}
+ addNew={false}
+ onSelect={this.changeColumns}
+ setIsEditing={this.setHeaderIsEditing}
+ />;
+ }
+
+ @undoBatch
+ @action
+ deleteColumn = (key: string) => {
+ const columns = this.columns;
+ if (columns === undefined) {
+ this.columns = new List<SchemaHeaderField>([]);
+ } else {
+ const index = columns.map(c => c.heading).indexOf(key);
+ if (index > -1) {
+ columns.splice(index, 1);
+ this.columns = columns;
+ }
+ }
+ this.closeHeader();
+ }
+
+ getPreviewTransform = (): Transform => {
+ return this.props.ScreenToLocalTransform().translate(- this.borderWidth - NumCast(COLLECTION_BORDER_WIDTH) - this.tableWidth, - this.borderWidth);
+ }
+
+ @action
+ onHeaderClick = (e: React.PointerEvent) => {
+ this.props.active(true);
+ e.stopPropagation();
+ }
+
+ @action
+ onWheel(e: React.WheelEvent) {
+ const scale = this.props.ScreenToLocalTransform().Scale;
+ this.props.active(true) && e.stopPropagation();
+ //this.menuCoordinates[0] -= e.screenX / scale;
+ //this.menuCoordinates[1] -= e.screenY / scale;
+ }
+
+ @computed get renderMenuContent() {
+ TraceMobx();
+ return <div className="collectionSchema-header-menuOptions">
+ <div className="collectionSchema-headerMenu-group">
+ <label>Key:</label>
+ {this.renderKeysDropDown(this._col)}
+ </div>
+ {this.renderTypes(this._col)}
+ {this.renderSorting(this._col)}
+ {this.renderColors(this._col)}
+ <div className="collectionSchema-headerMenu-group">
+ <button onClick={() => { this.deleteColumn(this._col.heading); }}
+ >Delete Column</button>
+ </div>
+ </div>;
+ }
+
private createTarget = (ele: HTMLDivElement) => {
this._previewCont = ele;
super.CreateDropTarget(ele);
@@ -105,14 +448,12 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
@computed
get previewDocument(): Doc | undefined { return this.previewDoc; }
- getPreviewTransform = (): Transform => {
- return this.props.ScreenToLocalTransform().translate(- this.borderWidth - this.DIVIDER_WIDTH - this.tableWidth, - this.borderWidth);
- }
-
@computed
get dividerDragger() {
return this.previewWidth() === 0 ? (null) :
- <div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown} style={{ width: `${this.DIVIDER_WIDTH}px` }} />;
+ <div className="collectionSchemaView-dividerDragger"
+ onPointerDown={this.onDividerDown}
+ style={{ width: `${this.DIVIDER_WIDTH}px` }} />;
}
@computed
@@ -174,6 +515,17 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
deleteDocument={this.props.removeDocument}
addDocument={this.props.addDocument}
dataDoc={this.props.DataDoc}
+ columns={this.columns}
+ documentKeys={this.documentKeys}
+ headerIsEditing={this._headerIsEditing}
+ openHeader={this.openHeader}
+ onPointerDown={this.onTablePointerDown}
+ onResizedChange={this.onResizedChange}
+ setColumns={this.setColumns}
+ reorderColumns={this.reorderColumns}
+ changeColumns={this.changeColumns}
+ setHeaderIsEditing={this.setHeaderIsEditing}
+ changeColumnSort={this.setColumnSort}
/>;
}
@@ -181,390 +533,33 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
public get schemaToolbar() {
return <div className="collectionSchemaView-toolbar">
<div className="collectionSchemaView-toolbar-item">
- <div id="preview-schema-checkbox-div"><input type="checkbox" key={"Show Preview"} checked={this.previewWidth() !== 0} onChange={this.toggleExpander} />Show Preview</div>
+ <div id="preview-schema-checkbox-div">
+ <input type="checkbox"
+ key={"Show Preview"} checked={this.previewWidth() !== 0}
+ onChange={this.toggleExpander} />Show Preview</div>
</div>
</div>;
}
- render() {
- return <div className="collectionSchemaView-container"
- style={{
- pointerEvents: !this.props.active() && !SnappingManager.GetIsDragging() ? "none" : undefined,
- width: this.props.PanelWidth() || "100%", height: this.props.PanelHeight() || "100%"
- }} >
- <div className="collectionSchemaView-tableContainer" style={{ width: `calc(100% - ${this.previewWidth()}px)` }} onPointerDown={this.onPointerDown} onWheel={e => this.props.active(true) && e.stopPropagation()} onDrop={e => this.onExternalDrop(e, {})} ref={this.createTarget}>
- {this.schemaTable}
- </div>
- {this.dividerDragger}
- {!this.previewWidth() ? (null) : this.previewPanel}
- </div>;
- }
-}
-
-export interface SchemaTableProps {
- Document: Doc; // child doc
- dataDoc?: Doc;
- PanelHeight: () => number;
- PanelWidth: () => number;
- childDocs?: Doc[];
- CollectionView: Opt<CollectionView>;
- ContainingCollectionView: Opt<CollectionView>;
- ContainingCollectionDoc: Opt<Doc>;
- fieldKey: string;
- renderDepth: number;
- deleteDocument: (document: Doc | Doc[]) => boolean;
- addDocument: (document: Doc | Doc[]) => boolean;
- moveDocument: (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
- ScreenToLocalTransform: () => Transform;
- active: (outsideReaction: boolean) => boolean;
- onDrop: (e: React.DragEvent<Element>, options: DocumentOptions, completed?: (() => void) | undefined) => void;
- addDocTab: (document: Doc, where: string) => boolean;
- pinToPres: (document: Doc) => void;
- isSelected: (outsideReaction?: boolean) => boolean;
- isFocused: (document: Doc) => boolean;
- setFocused: (document: Doc) => void;
- setPreviewDoc: (document: Doc) => void;
-}
-
-@observer
-export class SchemaTable extends React.Component<SchemaTableProps> {
- private DIVIDER_WIDTH = 4;
-
- @observable _headerIsEditing: boolean = false;
- @observable _cellIsEditing: boolean = false;
- @observable _focusedCell: { row: number, col: number } = { row: 0, col: 0 };
- @observable _openCollections: Array<string> = [];
-
- @computed get previewWidth() { return () => NumCast(this.props.Document.schemaPreviewWidth); }
- @computed get previewHeight() { return () => this.props.PanelHeight() - 2 * this.borderWidth; }
- @computed get tableWidth() { return this.props.PanelWidth() - 2 * this.borderWidth - this.DIVIDER_WIDTH - this.previewWidth(); }
-
- @computed get columns() {
- return Cast(this.props.Document._schemaHeaders, listSpec(SchemaHeaderField), []);
- }
- set columns(columns: SchemaHeaderField[]) {
- this.props.Document._schemaHeaders = new List<SchemaHeaderField>(columns);
- }
-
- @computed get childDocs() {
- if (this.props.childDocs) return this.props.childDocs;
-
- const doc = this.props.dataDoc ? this.props.dataDoc : this.props.Document;
- return DocListCast(doc[this.props.fieldKey]);
- }
- set childDocs(docs: Doc[]) {
- const doc = this.props.dataDoc ? this.props.dataDoc : this.props.Document;
- doc[this.props.fieldKey] = new List<Doc>(docs);
- }
-
- @computed get textWrappedRows() {
- return Cast(this.props.Document.textwrappedSchemaRows, listSpec("string"), []);
- }
- set textWrappedRows(textWrappedRows: string[]) {
- this.props.Document.textwrappedSchemaRows = new List<string>(textWrappedRows);
- }
-
- @computed get resized(): { id: string, value: number }[] {
- return this.columns.reduce((resized, shf) => {
- (shf.width > -1) && resized.push({ id: shf.heading, value: shf.width });
- return resized;
- }, [] as { id: string, value: number }[]);
- }
- @computed get sorted(): SortingRule[] {
- return this.columns.reduce((sorted, shf) => {
- shf.desc && sorted.push({ id: shf.heading, desc: shf.desc });
- return sorted;
- }, [] as SortingRule[]);
- }
-
- @computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); }
- @computed get tableColumns(): Column<Doc>[] {
- const possibleKeys = this.documentKeys.filter(key => this.columns.findIndex(existingKey => existingKey.heading.toUpperCase() === key.toUpperCase()) === -1);
- const columns: Column<Doc>[] = [];
- const tableIsFocused = this.props.isFocused(this.props.Document);
- const focusedRow = this._focusedCell.row;
- const focusedCol = this._focusedCell.col;
- const isEditable = !this._headerIsEditing;
-
- if (this.childDocs.reduce((found, doc) => found || doc.type === "collection", false)) {
- columns.push(
- {
- expander: true,
- Header: "",
- width: 30,
- Expander: (rowInfo) => {
- if (rowInfo.original.type === "collection") {
- if (rowInfo.isExpanded) return <div className="collectionSchemaView-expander" onClick={() => this.onCloseCollection(rowInfo.original)}><FontAwesomeIcon icon={"sort-up"} size="sm" /></div>;
- if (!rowInfo.isExpanded) return <div className="collectionSchemaView-expander" onClick={() => this.onExpandCollection(rowInfo.original)}><FontAwesomeIcon icon={"sort-down"} size="sm" /></div>;
- } else {
- return null;
- }
- }
- }
- );
- }
-
- const cols = this.columns.map(col => {
- const header = <CollectionSchemaHeader
- keyValue={col}
- possibleKeys={possibleKeys}
- existingKeys={this.columns.map(c => c.heading)}
- keyType={this.getColumnType(col)}
- typeConst={columnTypes.get(col.heading) !== undefined}
- onSelect={this.changeColumns}
- setIsEditing={this.setHeaderIsEditing}
- deleteColumn={this.deleteColumn}
- setColumnType={this.setColumnType}
- setColumnSort={this.setColumnSort}
- setColumnColor={this.setColumnColor}
- />;
-
- return {
- Header: <MovableColumn columnRenderer={header} columnValue={col} allColumns={this.columns} reorderColumns={this.reorderColumns} ScreenToLocalTransform={this.props.ScreenToLocalTransform} />,
- accessor: (doc: Doc) => doc ? doc[col.heading] : 0,
- id: col.heading,
- Cell: (rowProps: CellInfo) => {
- const rowIndex = rowProps.index;
- const columnIndex = this.columns.map(c => c.heading).indexOf(rowProps.column.id!);
- const isFocused = focusedRow === rowIndex && focusedCol === columnIndex && tableIsFocused;
-
- const props: CellProps = {
- row: rowIndex,
- col: columnIndex,
- rowProps: rowProps,
- isFocused: isFocused,
- changeFocusedCellByIndex: this.changeFocusedCellByIndex,
- CollectionView: this.props.CollectionView,
- ContainingCollection: this.props.ContainingCollectionView,
- Document: this.props.Document,
- fieldKey: this.props.fieldKey,
- renderDepth: this.props.renderDepth,
- addDocTab: this.props.addDocTab,
- pinToPres: this.props.pinToPres,
- moveDocument: this.props.moveDocument,
- setIsEditing: this.setCellIsEditing,
- isEditable: isEditable,
- setPreviewDoc: this.props.setPreviewDoc,
- setComputed: this.setComputed,
- getField: this.getField,
- };
-
- const colType = this.getColumnType(col);
- if (colType === ColumnType.Number) return <CollectionSchemaNumberCell {...props} />;
- if (colType === ColumnType.String) return <CollectionSchemaStringCell {...props} />;
- if (colType === ColumnType.Boolean) return <CollectionSchemaCheckboxCell {...props} />;
- if (colType === ColumnType.Doc) return <CollectionSchemaDocCell {...props} />;
- return <CollectionSchemaCell {...props} />;
- },
- minWidth: 200,
- };
- });
- columns.push(...cols);
-
- columns.push({
- Header: <CollectionSchemaAddColumnHeader createColumn={this.createColumn} />,
- accessor: (doc: Doc) => 0,
- id: "add",
- Cell: (rowProps: CellInfo) => <></>,
- width: 28,
- resizable: false
- });
- return columns;
- }
-
- constructor(props: SchemaTableProps) {
- super(props);
- // convert old schema columns (list of strings) into new schema columns (list of schema header fields)
- const oldSchemaHeaders = Cast(this.props.Document._schemaHeaders, listSpec("string"), []);
- if (oldSchemaHeaders?.length && typeof oldSchemaHeaders[0] !== "object") {
- const newSchemaHeaders = oldSchemaHeaders.map(i => typeof i === "string" ? new SchemaHeaderField(i, "#f1efeb") : i);
- this.props.Document._schemaHeaders = new List<SchemaHeaderField>(newSchemaHeaders);
- } else if (this.props.Document._schemaHeaders === undefined) {
- this.props.Document._schemaHeaders = new List<SchemaHeaderField>([new SchemaHeaderField("title", "#f1efeb")]);
- }
- }
-
- componentDidMount() {
- document.addEventListener("keydown", this.onKeyDown);
- }
-
- componentWillUnmount() {
- document.removeEventListener("keydown", this.onKeyDown);
- }
-
- tableAddDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => {
- return Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before);
- }
-
- private getTrProps: ComponentPropsGetterR = (state, rowInfo) => {
- return !rowInfo ? {} : {
- ScreenToLocalTransform: this.props.ScreenToLocalTransform,
- addDoc: this.tableAddDoc,
- removeDoc: this.props.deleteDocument,
- rowInfo,
- rowFocused: !this._headerIsEditing && rowInfo.index === this._focusedCell.row && this.props.isFocused(this.props.Document),
- textWrapRow: this.toggleTextWrapRow,
- rowWrapped: this.textWrappedRows.findIndex(id => rowInfo.original[Id] === id) > -1,
- dropAction: StrCast(this.props.Document.childDropAction)
- };
- }
-
- private getTdProps: ComponentPropsGetterR = (state, rowInfo, column, instance) => {
- if (!rowInfo || column) return {};
-
- const row = rowInfo.index;
- //@ts-ignore
- const col = this.columns.map(c => c.heading).indexOf(column!.id);
- const isFocused = this._focusedCell.row === row && this._focusedCell.col === col && this.props.isFocused(this.props.Document);
- // TODO: editing border doesn't work :(
- return {
- style: {
- border: !this._headerIsEditing && isFocused ? "2px solid rgb(255, 160, 160)" : "1px solid #f1efeb"
- }
- };
- }
-
@action
- onCloseCollection = (collection: Doc): void => {
- const index = this._openCollections.findIndex(col => col === collection[Id]);
- if (index > -1) this._openCollections.splice(index, 1);
- }
-
- @action onExpandCollection = (collection: Doc) => this._openCollections.push(collection[Id]);
- @action setCellIsEditing = (isEditing: boolean) => this._cellIsEditing = isEditing;
- @action setHeaderIsEditing = (isEditing: boolean) => this._headerIsEditing = isEditing;
-
- onPointerDown = (e: React.PointerEvent): void => {
- this.props.setFocused(this.props.Document);
+ onTablePointerDown = (e: React.PointerEvent): void => {
+ this.setFocused(this.props.Document);
if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey && this.props.isSelected(true)) {
e.stopPropagation();
}
+ this._pointerY = e.screenY;
+ this._pointerX = e.screenX;
}
- @action
- onKeyDown = (e: KeyboardEvent): void => {
- if (!this._cellIsEditing && !this._headerIsEditing && this.props.isFocused(this.props.Document)) {// && this.props.isSelected(true)) {
- const direction = e.key === "Tab" ? "tab" : e.which === 39 ? "right" : e.which === 37 ? "left" : e.which === 38 ? "up" : e.which === 40 ? "down" : "";
- this._focusedCell = this.changeFocusedCellByDirection(direction, this._focusedCell.row, this._focusedCell.col);
-
- const pdoc = FieldValue(this.childDocs[this._focusedCell.row]);
- pdoc && this.props.setPreviewDoc(pdoc);
- }
- }
-
- changeFocusedCellByDirection = (direction: string, curRow: number, curCol: number) => {
- switch (direction) {
- case "tab": return { row: (curRow + 1 === this.childDocs.length ? 0 : curRow + 1), col: curCol + 1 === this.columns.length ? 0 : curCol + 1 };
- case "right": return { row: curRow, col: curCol + 1 === this.columns.length ? curCol : curCol + 1 };
- case "left": return { row: curRow, col: curCol === 0 ? curCol : curCol - 1 };
- case "up": return { row: curRow === 0 ? curRow : curRow - 1, col: curCol };
- case "down": return { row: curRow + 1 === this.childDocs.length ? curRow : curRow + 1, col: curCol };
- }
- return this._focusedCell;
- }
-
- @action
- changeFocusedCellByIndex = (row: number, col: number): void => {
- if (this._focusedCell.row !== row || this._focusedCell.col !== col) {
- this._focusedCell = { row: row, col: col };
- }
- this.props.setFocused(this.props.Document);
- }
-
- @undoBatch
- createRow = () => {
- this.props.addDocument(Docs.Create.TextDocument("", { title: "", _width: 100, _height: 30 }));
- }
-
- @undoBatch
- @action
- createColumn = () => {
- let index = 0;
- let found = this.columns.findIndex(col => col.heading.toUpperCase() === "New field".toUpperCase()) > -1;
- while (found) {
- index++;
- found = this.columns.findIndex(col => col.heading.toUpperCase() === ("New field (" + index + ")").toUpperCase()) > -1;
- }
- this.columns.push(new SchemaHeaderField(`New field ${index ? "(" + index + ")" : ""}`, "#f1efeb"));
- }
-
- @undoBatch
- @action
- deleteColumn = (key: string) => {
- const columns = this.columns;
- if (columns === undefined) {
- this.columns = new List<SchemaHeaderField>([]);
- } else {
- const index = columns.map(c => c.heading).indexOf(key);
- if (index > -1) {
- columns.splice(index, 1);
- this.columns = columns;
- }
- }
- }
-
- @undoBatch
- @action
- changeColumns = (oldKey: string, newKey: string, addNew: boolean) => {
- const columns = this.columns;
- if (columns === undefined) {
- this.columns = new List<SchemaHeaderField>([new SchemaHeaderField(newKey, "f1efeb")]);
- } else {
- if (addNew) {
- columns.push(new SchemaHeaderField(newKey, "f1efeb"));
- this.columns = columns;
- } else {
- const index = columns.map(c => c.heading).indexOf(oldKey);
- if (index > -1) {
- const column = columns[index];
- column.setHeading(newKey);
- columns[index] = column;
- this.columns = columns;
- }
- }
- }
- }
-
- getColumnType = (column: SchemaHeaderField): ColumnType => {
- // added functionality to convert old column type stuff to new column type stuff -syip
- if (column.type && column.type !== 0) {
- return column.type;
- }
- if (columnTypes.get(column.heading)) {
- column.type = columnTypes.get(column.heading)!;
- return columnTypes.get(column.heading)!;
- }
- const typesDoc = FieldValue(Cast(this.props.Document.schemaColumnTypes, Doc));
- if (!typesDoc) {
- column.type = ColumnType.Any;
- return ColumnType.Any;
- }
- column.type = NumCast(typesDoc[column.heading]);
- return NumCast(typesDoc[column.heading]);
- }
-
- @undoBatch
- setColumnType = (columnField: SchemaHeaderField, type: ColumnType): void => {
- if (columnTypes.get(columnField.heading)) return;
-
- const columns = this.columns;
- const index = columns.indexOf(columnField);
- if (index > -1) {
- columnField.setType(NumCast(type));
- columns[index] = columnField;
- this.columns = columns;
- }
- }
-
- @undoBatch
- setColumnColor = (columnField: SchemaHeaderField, color: string): void => {
+ onResizedChange = (newResized: Resize[], event: any) => {
const columns = this.columns;
- const index = columns.indexOf(columnField);
- if (index > -1) {
- columnField.setColor(color);
- columns[index] = columnField;
- this.columns = columns; // need to set the columns to trigger rerender
- }
+ newResized.forEach(resized => {
+ const index = columns.findIndex(c => c.heading === resized.id);
+ const column = columns[index];
+ column.setWidth(resized.value);
+ columns[index] = column;
+ });
+ this.columns = columns;
}
@action
@@ -583,180 +578,54 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
this.columns = columns;
}
- @undoBatch
- @action
- setColumnSort = (columnField: SchemaHeaderField, descending: boolean | undefined) => {
- const columns = this.columns;
- const index = columns.findIndex(c => c.heading === columnField.heading);
- const column = columns[index];
- column.setDesc(descending);
- columns[index] = column;
- this.columns = columns;
- }
-
- get documentKeys() {
- const docs = this.childDocs;
- const keys: { [key: string]: boolean } = {};
- // bcz: ugh. this is untracked since otherwise a large collection of documents will blast the server for all their fields.
- // then as each document's fields come back, we update the documents _proxies. Each time we do this, the whole schema will be
- // invalidated and re-rendered. This workaround will inquire all of the document fields before the options button is clicked.
- // then by the time the options button is clicked, all of the fields should be in place. If a new field is added while this menu
- // is displayed (unlikely) it won't show up until something else changes.
- //TODO Types
- untracked(() => docs.map(doc => Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => keys[key] = false))));
-
- this.columns.forEach(key => keys[key.heading] = true);
- return Array.from(Object.keys(keys));
- }
-
- @undoBatch
- @action
- toggleTextwrap = async () => {
- const textwrappedRows = Cast(this.props.Document.textwrappedSchemaRows, listSpec("string"), []);
- if (textwrappedRows.length) {
- this.props.Document.textwrappedSchemaRows = new List<string>([]);
+ onZoomMenu = (e: React.WheelEvent) => {
+ this.props.active(true) && e.stopPropagation();
+ if (this.menuCoordinates[0] > e.screenX) {
+ this.menuCoordinates[0] -= e.screenX; //* this.scale;
} else {
- const docs = DocListCast(this.props.Document[this.props.fieldKey]);
- const allRows = docs instanceof Doc ? [docs[Id]] : docs.map(doc => doc[Id]);
- this.props.Document.textwrappedSchemaRows = new List<string>(allRows);
- }
- }
-
- @action
- toggleTextWrapRow = (doc: Doc): void => {
- const textWrapped = this.textWrappedRows;
- const index = textWrapped.findIndex(id => doc[Id] === id);
-
- index > -1 ? textWrapped.splice(index, 1) : textWrapped.push(doc[Id]);
-
- this.textWrappedRows = textWrapped;
- }
-
- @computed
- get reactTable() {
- const children = this.childDocs;
- const hasCollectionChild = children.reduce((found, doc) => found || doc.type === "collection", false);
- const expandedRowsList = this._openCollections.map(col => children.findIndex(doc => doc[Id] === col).toString());
- const expanded = {};
- //@ts-ignore
- expandedRowsList.forEach(row => expanded[row] = true);
- const rerender = [...this.textWrappedRows]; // TODO: get component to rerender on text wrap change without needign to console.log :((((
-
- return <ReactTable
- style={{ position: "relative" }}
- data={children}
- page={0}
- pageSize={children.length}
- showPagination={false}
- columns={this.tableColumns}
- getTrProps={this.getTrProps}
- getTdProps={this.getTdProps}
- sortable={false}
- TrComponent={MovableRow}
- sorted={this.sorted}
- expanded={expanded}
- resized={this.resized}
- onResizedChange={this.onResizedChange}
- SubComponent={!hasCollectionChild ? undefined : row => (row.original.type !== "collection") ? (null) :
- <div className="reactTable-sub"><SchemaTable {...this.props} Document={row.original} dataDoc={undefined} childDocs={undefined} /></div>}
-
- />;
- }
-
- onResizedChange = (newResized: Resize[], event: any) => {
- const columns = this.columns;
- newResized.forEach(resized => {
- const index = columns.findIndex(c => c.heading === resized.id);
- const column = columns[index];
- column.setWidth(resized.value);
- columns[index] = column;
- });
- this.columns = columns;
- }
-
- onContextMenu = (e: React.MouseEvent): void => {
- if (!e.isPropagationStopped() && this.props.Document[Id] !== "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
- // ContextMenu.Instance.addItem({ description: "Make DB", event: this.makeDB, icon: "table" });
- ContextMenu.Instance.addItem({ description: "Toggle text wrapping", event: this.toggleTextwrap, icon: "table" });
+ this.menuCoordinates[0] += e.screenX; //* this.scale;
}
- }
-
- getField = (row: number, col?: number) => {
- const docs = this.childDocs;
-
- row = row % docs.length;
- while (row < 0) row += docs.length;
- const columns = this.columns;
- const doc = docs[row];
- if (col === undefined) {
- return doc;
- }
- if (col >= 0 && col < columns.length) {
- const column = this.columns[col].heading;
- return doc[column];
- }
- return undefined;
- }
-
- createTransformer = (row: number, col: number): Transformer => {
- const self = this;
- const captures: { [name: string]: Field } = {};
-
- const transformer: ts.TransformerFactory<ts.SourceFile> = context => {
- return root => {
- function visit(node: ts.Node) {
- node = ts.visitEachChild(node, visit, context);
- if (ts.isIdentifier(node)) {
- const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node;
- const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node;
- if (isntPropAccess && isntPropAssign) {
- if (node.text === "$r") {
- return ts.createNumericLiteral(row.toString());
- } else if (node.text === "$c") {
- return ts.createNumericLiteral(col.toString());
- } else if (node.text === "$") {
- if (ts.isCallExpression(node.parent)) {
- // captures.doc = self.props.Document;
- // captures.key = self.props.fieldKey;
- }
- }
- }
- }
-
- return node;
- }
- return ts.visitNode(root, visit);
- };
- };
-
- // const getVars = () => {
- // return { capturedVariables: captures };
- // };
-
- return { transformer, /*getVars*/ };
- }
-
- setComputed = (script: string, doc: Doc, field: string, row: number, col: number): boolean => {
- script =
- `const $ = (row:number, col?:number) => {
- if(col === undefined) {
- return (doc as any)[key][row + ${row}];
- }
- return (doc as any)[key][row + ${row}][(doc as any)._schemaHeaders[col + ${col}].heading];
- }
- return ${script}`;
- const compiled = CompileScript(script, { params: { this: Doc.name }, capturedVariables: { doc: this.props.Document, key: this.props.fieldKey }, typecheck: false, transformer: this.createTransformer(row, col) });
- if (compiled.compiled) {
- doc[field] = new ComputedField(compiled);
- return true;
+ if (this.menuCoordinates[1] > e.screenY) {
+ this.menuCoordinates[1] -= e.screenY; //* this.scale;
+ } else {
+ this.menuCoordinates[1] += e.screenY; //* this.scale;
}
- return false;
}
render() {
- return <div className="collectionSchemaView-table" onPointerDown={this.onPointerDown} onWheel={e => this.props.active(true) && e.stopPropagation()} onDrop={e => this.props.onDrop(e, {})} onContextMenu={this.onContextMenu} >
- {this.reactTable}
- <div className="collectionSchemaView-addRow" onClick={() => this.createRow()}>+ new</div>
+ TraceMobx();
+ const menuContent = this.renderMenuContent;
+ const menu = <div className="collectionSchema-header-menu" ref={this.setNode}
+ onWheel={e => this.onZoomMenu(e)}
+ onPointerDown={e => this.onHeaderClick(e)}
+ style={{
+ position: "fixed", background: "white",
+ transform: `translate(${this.menuCoordinates[0] / this.scale}px, ${this.menuCoordinates[1] / this.scale}px)`
+ }}>
+ <Measure offset onResize={action((r: any) => {
+ const dim = this.props.ScreenToLocalTransform().inverse().transformDirection(r.offset.width, r.offset.height);
+ this._menuWidth = dim[0]; this._menuHeight = dim[1];
+ })}>
+ {({ measureRef }) => <div ref={measureRef}> {menuContent} </div>}
+ </Measure>
+ </div>;
+
+ return <div className="collectionSchemaView-container"
+ style={{
+ pointerEvents: !this.props.active() && !SnappingManager.GetIsDragging() ? "none" : undefined,
+ width: this.props.PanelWidth() || "100%", height: this.props.PanelHeight() || "100%"
+ }} >
+ <div className="collectionSchemaView-tableContainer"
+ style={{ width: `calc(100% - ${this.previewWidth()}px)` }}
+ onPointerDown={this.onPointerDown}
+ onWheel={e => this.props.active(true) && e.stopPropagation()}
+ onDrop={e => this.onExternalDrop(e, {})}
+ ref={this.createTarget}>
+ {this.schemaTable}
+ </div>
+ {this.dividerDragger}
+ {!this.previewWidth() ? (null) : this.previewPanel}
+ {this._headerOpen ? menu : null}
</div>;
}
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index 3d8ec2fd5..8fc74a9c6 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -33,8 +33,9 @@
.collectionStackingViewFieldColumn {
height: max-content;
}
+
.collectionStackingViewFieldColumnDragging {
- height:100%;
+ height: 100%;
}
.collectionSchemaView-previewDoc {
@@ -425,4 +426,15 @@
.rc-switch-checked .rc-switch-inner {
left: 8px;
}
+}
+
+@media only screen and (max-device-width: 480px) {
+
+ .collectionStackingView .collectionStackingView-columnDragger,
+ .collectionMasonryView .collectionStackingView-columnDragger {
+ width: 0.1;
+ height: 0.1;
+ opacity: 0;
+ font-size: 0;
+ }
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 8bd32b81f..ced234850 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -498,4 +498,4 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
</div> </div>
);
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index d107db86b..ed8535ecb 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -9,7 +9,6 @@ import { ScriptField } from "../../../fields/ScriptField";
import { WebField } from "../../../fields/URLField";
import { Cast, ScriptCast, NumCast, StrCast } from "../../../fields/Types";
import { GestureUtils } from "../../../pen-gestures/GestureUtils";
-import { Upload } from "../../../server/SharedMediaTypes";
import { Utils, returnFalse, returnEmptyFilter } from "../../../Utils";
import { DocServer } from "../../DocServer";
import { Networking } from "../../Network";
@@ -225,7 +224,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
const movedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] === d);
const addedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] !== d);
const res = addedDocs.length ? this.addDocument(addedDocs) : true;
- added = movedDocs.length ? docDragData.moveDocument(movedDocs, this.props.Document, de.embedKey || !this.props.isAnnotationOverlay ? this.addDocument : returnFalse) : res;
+ added = movedDocs.length ? docDragData.moveDocument(movedDocs, this.props.Document, Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.props.Document) || de.embedKey || !this.props.isAnnotationOverlay ? this.addDocument : returnFalse) : res;
} else {
added = this.addDocument(docDragData.droppedDocuments);
}
@@ -238,21 +237,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
}
return false;
}
- readUploadedFileAsText = (inputFile: File) => {
- const temporaryFileReader = new FileReader();
- return new Promise((resolve, reject) => {
- temporaryFileReader.onerror = () => {
- temporaryFileReader.abort();
- reject(new DOMException("Problem parsing input file."));
- };
-
- temporaryFileReader.onload = () => {
- resolve(temporaryFileReader.result);
- };
- temporaryFileReader.readAsText(inputFile);
- });
- }
@undoBatch
@action
protected async onExternalDrop(e: React.DragEvent, options: DocumentOptions, completed?: () => void) {
@@ -271,8 +256,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
e.stopPropagation();
e.preventDefault();
- const { addDocument } = this;
- if (!addDocument) {
+ if (!this.addDocument) {
alert("this.props.addDocument does not exist. Aborting drop operation.");
return;
}
@@ -286,14 +270,14 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
DocServer.GetRefField(docid).then(f => {
if (f instanceof Doc) {
if (options.x || options.y) { f.x = options.x; f.y = options.y; } // should be in CollectionFreeFormView
- (f instanceof Doc) && addDocument(f);
+ (f instanceof Doc) && this.addDocument(f);
}
});
} else {
- addDocument(Docs.Create.WebDocument(href, { ...options, title: href }));
+ this.addDocument(Docs.Create.WebDocument(href, { ...options, title: href }));
}
} else if (text) {
- addDocument(Docs.Create.TextDocument(text, { ...options, _width: 100, _height: 25 }));
+ this.addDocument(Docs.Create.TextDocument(text, { ...options, _width: 100, _height: 25 }));
}
return;
}
@@ -313,7 +297,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
if (source.startsWith("http")) {
const doc = Docs.Create.ImageDocument(source, { ...options, _width: 300 });
ImageUtils.ExtractExif(doc);
- addDocument(doc);
+ this.addDocument(doc);
}
return;
} else {
@@ -360,7 +344,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
if (text) {
if (text.includes("www.youtube.com/watch") || text.includes("www.youtube.com/embed")) {
const url = text.replace("youtube.com/watch?v=", "youtube.com/embed/").split("&")[0];
- addDocument(Docs.Create.VideoDocument(url, {
+ this.addDocument(Docs.Create.VideoDocument(url, {
...options,
title: url,
_width: 400,
@@ -413,10 +397,10 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
const file = item.getAsFile();
file?.type && files.push(file);
- file?.type === "application/json" && this.readUploadedFileAsText(file).then(result => {
+ file?.type === "application/json" && Utils.readUploadedFileAsText(file).then(result => {
console.log(result);
const json = JSON.parse(result as string);
- addDocument(Docs.Create.TreeDocument(
+ this.addDocument(Docs.Create.TreeDocument(
json["rectangular-puzzle"].crossword.clues[0].clue.map((c: any) => {
const label = Docs.Create.LabelDocument({ title: c["#text"], _width: 120, _height: 20 });
const proto = Doc.GetProto(label);
@@ -428,38 +412,18 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
});
}
}
- for (const { source: { name, type }, result } of await Networking.UploadFilesToServer(files)) {
- if (result instanceof Error) {
- alert(`Upload failed: ${result.message}`);
- return;
- }
- const full = { ...options, _width: 400, title: name };
- const pathname = Utils.prepend(result.accessPaths.agnostic.client);
- const doc = await DocUtils.DocumentFromType(type, pathname, full);
- if (!doc) {
- continue;
- }
- const proto = Doc.GetProto(doc);
- proto.text = result.rawText;
- proto.fileUpload = basename(pathname).replace("upload_", "").replace(/\.[a-z0-9]*$/, "");
- if (Upload.isImageInformation(result)) {
- proto["data-nativeWidth"] = (result.nativeWidth > result.nativeHeight) ? 400 * result.nativeWidth / result.nativeHeight : 400;
- proto["data-nativeHeight"] = (result.nativeWidth > result.nativeHeight) ? 400 : 400 / (result.nativeWidth / result.nativeHeight);
- proto.contentSize = result.contentSize;
- }
- generatedDocuments.push(doc);
- }
+ generatedDocuments.push(...await DocUtils.uploadFilesToDocs(files, options));
if (generatedDocuments.length) {
const set = generatedDocuments.length > 1 && generatedDocuments.map(d => DocUtils.iconify(d));
if (set) {
- addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!)!);
+ this.addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!)!);
} else {
- generatedDocuments.forEach(addDocument);
+ generatedDocuments.forEach(this.addDocument);
}
completed?.();
} else {
if (text && !text.includes("https://")) {
- addDocument(Docs.Create.TextDocument(text, { ...options, _width: 400, _height: 315 }));
+ this.addDocument(Docs.Create.TextDocument(text, { ...options, _width: 400, _height: 315 }));
}
}
batch.end();
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 6acf78af7..26abd2529 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -12,26 +12,29 @@ import { AclAddonly, AclReadonly, AclSym, DataSym, Doc, DocListCast, Field, Opt
import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
import { ObjectField } from '../../../fields/ObjectField';
+import { RichTextField } from '../../../fields/RichTextField';
import { listSpec } from '../../../fields/Schema';
import { ComputedField, ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, emptyPath, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils, returnEmptyFilter } from '../../../Utils';
-import { Docs } from '../../documents/Documents';
+import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
+import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
import { CurrentUserUtils } from '../../util/CurrentUserUtils';
import { ImageUtils } from '../../util/Import & Export/ImageUtils';
import { InteractionUtils } from '../../util/InteractionUtils';
+import { UndoManager } from '../../util/UndoManager';
import { ContextMenu } from "../ContextMenu";
import { FieldView, FieldViewProps } from '../nodes/FieldView';
import { ScriptBox } from '../ScriptBox';
import { Touchable } from '../Touchable';
-import { CollectionCarouselView } from './CollectionCarouselView';
import { CollectionCarousel3DView } from './CollectionCarousel3DView';
+import { CollectionCarouselView } from './CollectionCarouselView';
import { CollectionDockingView } from "./CollectionDockingView";
import { AddCustomFreeFormLayout } from './collectionFreeForm/CollectionFreeFormLayoutEngines';
import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
+import { CollectionGridView } from './collectionGrid/CollectionGridView';
import { CollectionLinearView } from './CollectionLinearView';
import CollectionMapView from './CollectionMapView';
import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView';
@@ -43,12 +46,8 @@ import { CollectionStaffView } from './CollectionStaffView';
import { SubCollectionViewProps } from './CollectionSubView';
import { CollectionTimeView } from './CollectionTimeView';
import { CollectionTreeView } from "./CollectionTreeView";
-import { CollectionGridView } from './collectionGrid/CollectionGridView';
import './CollectionView.scss';
import { CollectionViewBaseChrome } from './CollectionViewChromes';
-import { UndoManager } from '../../util/UndoManager';
-import { RichTextField } from '../../../fields/RichTextField';
-import { TextField } from '../../util/ProsemirrorCopy/prompt';
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -139,7 +138,23 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
} else if (this.dataDoc[AclSym] === AclAddonly) {
added.map(doc => Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc));
} else {
- added.map(doc => doc.context = this.props.Document);
+ added.map(doc => {
+ const context = Cast(doc.context, Doc, null);
+ if (context && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG) &&
+ !DocListCast(doc.links).some(d => d.isPushpin)) {
+ const pushpin = Docs.Create.FontIconDocument({
+ icon: "map-pin", x: Cast(doc.x, "number", null), y: Cast(doc.y, "number", null), _backgroundColor: "#0000003d", color: "#ACCEF7",
+ _width: 15, _height: 15, _xPadding: 0, isLinkButton: true, displayTimecode: Cast(doc.displayTimecode, "number", null)
+ });
+ Doc.AddDocToList(context, Doc.LayoutFieldKey(context) + "-annotations", pushpin);
+ const pushpinLink = DocUtils.MakeLink({ doc: pushpin }, { doc: doc }, "pushpin");
+ const first = DocListCast(pushpin.links).find(d => d instanceof Doc);
+ first && (first.hidden = true);
+ pushpinLink && (Doc.GetProto(pushpinLink).isPushpin = true);
+ doc.displayTimecode = undefined;
+ }
+ doc.context = this.props.Document;
+ });
added.map(add => Doc.AddDocToList(Cast(Doc.UserDoc().myCatalog, Doc, null), "data", add));
targetDataDoc[this.props.fieldKey] = new List<Doc>([...docList, ...added]);
targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
@@ -164,7 +179,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
// this is called with the document that was dragged and the collection to move it into.
// if the target collection is the same as this collection, then the move will be allowed.
// otherwise, the document being moved must be able to be removed from its container before
- // moving it into the target.
+ // moving it into the target.
@action.bound
moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
if (Doc.AreProtosEqual(this.props.Document, targetCollection)) {
@@ -186,7 +201,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
showIsTagged = () => {
return (null);
- // this section would display an icon in the bototm right of a collection to indicate that all
+ // this section would display an icon in the bototm right of a collection to indicate that all
// photos had been processed through Google's content analysis API and Google's tags had been
// assigned to the documents googlePhotosTags field.
// const children = DocListCast(this.props.Document[this.props.fieldKey]);
@@ -261,7 +276,8 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
}
onContextMenu = (e: React.MouseEvent): void => {
- if (!e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
+ const cm = ContextMenu.Instance;
+ if (cm && !e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
this.setupViewTypes("Add a Perspective...", vtype => {
const newRendition = Doc.MakeAlias(this.props.Document);
newRendition._viewType = vtype;
@@ -269,7 +285,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
return newRendition;
}, false);
- const existing = ContextMenu.Instance.findByDescription("Options...");
+ const existing = cm.findByDescription("Options...");
const layoutItems = existing && "subitems" in existing ? existing.subitems : [];
layoutItems.push({ description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" });
if (this.props.Document.childLayout instanceof Doc) {
@@ -280,9 +296,9 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
}
layoutItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
- !existing && ContextMenu.Instance.addItem({ description: "Options...", subitems: layoutItems, icon: "hand-point-right" });
+ !existing && cm.addItem({ description: "Options...", subitems: layoutItems, icon: "hand-point-right" });
- const existingOnClick = ContextMenu.Instance.findByDescription("OnClick...");
+ const existingOnClick = cm.findByDescription("OnClick...");
const onClicks = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
const funcs = [
{ key: "onChildClick", name: "On Child Clicked" },
@@ -298,13 +314,13 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
icon: "edit",
event: () => this.props.Document[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data)),
}));
- !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
+ !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
if (!Doc.UserDoc().noviceMode) {
- const more = ContextMenu.Instance.findByDescription("More...");
+ const more = cm.findByDescription("More...");
const moreItems = more && "subitems" in more ? more.subitems : [];
moreItems.push({ description: "Export Image Hierarchy", icon: "columns", event: () => ImageUtils.ExportHierarchyToFileSystem(this.props.Document) });
- !more && ContextMenu.Instance.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
+ !more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
}
}
}
@@ -555,5 +571,3 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
</div>);
}
}
-
-
diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss
index 2885ac763..b1e8d20ad 100644
--- a/src/client/views/collections/CollectionViewChromes.scss
+++ b/src/client/views/collections/CollectionViewChromes.scss
@@ -16,6 +16,7 @@
height: 32px;
border-bottom: .5px solid rgb(180, 180, 180);
overflow: visible;
+ z-index: 9001;
.collectionViewBaseChrome {
display: flex;
@@ -80,6 +81,12 @@
// margin-top: 10px;
}
+ @media only screen and (max-device-width: 480px) {
+ .collectionViewBaseChrome-collapse {
+ display: none;
+ }
+ }
+
.collectionViewBaseChrome-template,
.collectionViewBaseChrome-viewModes {
display: grid;
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index ab0df88f8..7f1fe7649 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -300,7 +300,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
onPointerDown={stopPropagation}
onChange={this.viewChanged}
value={StrCast(this.props.CollectionView.props.Document._viewType)}>
- {Object.values(CollectionViewType).map(type => ["invalid", "docking"].includes(type) ? (null) : (
+ {Object.values(CollectionViewType).map(type => [CollectionViewType.Invalid, CollectionViewType.Docking].includes(type) ? (null) : (
<option
key={Utils.GenerateGuid()}
className="collectionViewBaseChrome-viewOption"
@@ -316,7 +316,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
render() {
const collapsed = this.props.CollectionView.props.Document._chromeStatus !== "enabled";
- const scale = Math.min(1, this.props.CollectionView.props.ScreenToLocalTransform().Scale);
+ const scale = Math.min(1, this.props.CollectionView.props.ScreenToLocalTransform()?.Scale);
return (
<div className="collectionViewChrome-cont" style={{
top: collapsed ? -70 : 0, height: collapsed ? 0 : undefined,
@@ -452,9 +452,9 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView
@action toggleSort = () => {
this.props.CollectionView.props.Document._columnsSort =
- this.props.CollectionView.props.Document._columnsSort === "descending" ? "ascending" :
- this.props.CollectionView.props.Document._columnsSort === "ascending" ? undefined : "descending";
- };
+ this.props.CollectionView.props.Document._columnsSort === "descending" ? "ascending" :
+ this.props.CollectionView.props.Document._columnsSort === "ascending" ? undefined : "descending";
+ }
@action resetValue = () => { this._currentKey = this.pivotField; };
render() {
diff --git a/src/client/views/collections/SchemaTable.tsx b/src/client/views/collections/SchemaTable.tsx
new file mode 100644
index 000000000..9e3b4d961
--- /dev/null
+++ b/src/client/views/collections/SchemaTable.tsx
@@ -0,0 +1,614 @@
+import React = require("react");
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, observable } from "mobx";
+import { observer } from "mobx-react";
+import ReactTable, { CellInfo, Column, ComponentPropsGetterR, Resize, SortingRule } from "react-table";
+import "react-table/react-table.css";
+import { Doc, DocListCast, Field, Opt } from "../../../fields/Doc";
+import { Id } from "../../../fields/FieldSymbols";
+import { List } from "../../../fields/List";
+import { listSpec } from "../../../fields/Schema";
+import { SchemaHeaderField } from "../../../fields/SchemaHeaderField";
+import { ComputedField } from "../../../fields/ScriptField";
+import { Cast, FieldValue, NumCast, StrCast, BoolCast } from "../../../fields/Types";
+import { Docs, DocumentOptions } from "../../documents/Documents";
+import { CompileScript, Transformer, ts } from "../../util/Scripting";
+import { Transform } from "../../util/Transform";
+import { undoBatch } from "../../util/UndoManager";
+import { COLLECTION_BORDER_WIDTH } from '../../views/globalCssVariables.scss';
+import { ContextMenu } from "../ContextMenu";
+import '../DocumentDecorations.scss';
+import { CellProps, CollectionSchemaCell, CollectionSchemaCheckboxCell, CollectionSchemaDocCell, CollectionSchemaNumberCell, CollectionSchemaStringCell, CollectionSchemaImageCell, CollectionSchemaListCell, CollectionSchemaDateCell } from "./CollectionSchemaCells";
+import { CollectionSchemaAddColumnHeader, KeysDropdown } from "./CollectionSchemaHeaders";
+import { MovableColumn, MovableRow } from "./CollectionSchemaMovableTableHOC";
+import "./CollectionSchemaView.scss";
+import { CollectionView } from "./CollectionView";
+import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView";
+import { emptyFunction, returnZero, returnOne, returnFalse, returnEmptyFilter, emptyPath } from "../../../Utils";
+import { TouchScrollableMenuItem } from "../TouchScrollableMenu";
+
+
+enum ColumnType {
+ Any,
+ Number,
+ String,
+ Boolean,
+ Doc,
+ Image,
+ List,
+ Date
+}
+
+// this map should be used for keys that should have a const type of value
+const columnTypes: Map<string, ColumnType> = new Map([
+ ["title", ColumnType.String],
+ ["x", ColumnType.Number], ["y", ColumnType.Number], ["_width", ColumnType.Number], ["_height", ColumnType.Number],
+ ["_nativeWidth", ColumnType.Number], ["_nativeHeight", ColumnType.Number], ["isPrototype", ColumnType.Boolean],
+ ["page", ColumnType.Number], ["curPage", ColumnType.Number], ["currentTimecode", ColumnType.Number], ["zIndex", ColumnType.Number]
+]);
+
+export interface SchemaTableProps {
+ Document: Doc; // child doc
+ dataDoc?: Doc;
+ PanelHeight: () => number;
+ PanelWidth: () => number;
+ childDocs?: Doc[];
+ CollectionView: Opt<CollectionView>;
+ ContainingCollectionView: Opt<CollectionView>;
+ ContainingCollectionDoc: Opt<Doc>;
+ fieldKey: string;
+ renderDepth: number;
+ deleteDocument: (document: Doc | Doc[]) => boolean;
+ addDocument: (document: Doc | Doc[]) => boolean;
+ moveDocument: (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
+ ScreenToLocalTransform: () => Transform;
+ active: (outsideReaction: boolean) => boolean;
+ onDrop: (e: React.DragEvent<Element>, options: DocumentOptions, completed?: (() => void) | undefined) => void;
+ addDocTab: (document: Doc, where: string) => boolean;
+ pinToPres: (document: Doc) => void;
+ isSelected: (outsideReaction?: boolean) => boolean;
+ isFocused: (document: Doc) => boolean;
+ setFocused: (document: Doc) => void;
+ setPreviewDoc: (document: Doc) => void;
+ columns: SchemaHeaderField[];
+ documentKeys: any[];
+ headerIsEditing: boolean;
+ openHeader: (column: any, screenx: number, screeny: number) => void;
+ onPointerDown: (e: React.PointerEvent) => void;
+ onResizedChange: (newResized: Resize[], event: any) => void;
+ setColumns: (columns: SchemaHeaderField[]) => void;
+ reorderColumns: (toMove: SchemaHeaderField, relativeTo: SchemaHeaderField, before: boolean, columnsValues: SchemaHeaderField[]) => void;
+ changeColumns: (oldKey: string, newKey: string, addNew: boolean) => void;
+ setHeaderIsEditing: (isEditing: boolean) => void;
+ changeColumnSort: (columnField: SchemaHeaderField, descending: boolean | undefined) => void;
+}
+
+@observer
+export class SchemaTable extends React.Component<SchemaTableProps> {
+ private DIVIDER_WIDTH = 4;
+
+ @observable _cellIsEditing: boolean = false;
+ @observable _focusedCell: { row: number, col: number } = { row: 0, col: 0 };
+ @observable _openCollections: Array<string> = [];
+
+ @observable _showDoc: Doc | undefined;
+ @observable _showDataDoc: any = "";
+ @observable _showDocPos: number[] = [];
+
+ @observable _showTitleDropdown: boolean = false;
+
+ @computed get previewWidth() { return () => NumCast(this.props.Document.schemaPreviewWidth); }
+ @computed get previewHeight() { return () => this.props.PanelHeight() - 2 * this.borderWidth; }
+ @computed get tableWidth() { return this.props.PanelWidth() - 2 * this.borderWidth - this.DIVIDER_WIDTH - this.previewWidth(); }
+
+ @computed get childDocs() {
+ if (this.props.childDocs) return this.props.childDocs;
+
+ const doc = this.props.dataDoc ? this.props.dataDoc : this.props.Document;
+ return DocListCast(doc[this.props.fieldKey]);
+ }
+ set childDocs(docs: Doc[]) {
+ const doc = this.props.dataDoc ? this.props.dataDoc : this.props.Document;
+ doc[this.props.fieldKey] = new List<Doc>(docs);
+ }
+
+ @computed get textWrappedRows() {
+ return Cast(this.props.Document.textwrappedSchemaRows, listSpec("string"), []);
+ }
+ set textWrappedRows(textWrappedRows: string[]) {
+ this.props.Document.textwrappedSchemaRows = new List<string>(textWrappedRows);
+ }
+
+ @computed get resized(): { id: string, value: number }[] {
+ return this.props.columns.reduce((resized, shf) => {
+ (shf.width > -1) && resized.push({ id: shf.heading, value: shf.width });
+ return resized;
+ }, [] as { id: string, value: number }[]);
+ }
+ @computed get sorted(): SortingRule[] {
+ return this.props.columns.reduce((sorted, shf) => {
+ shf.desc && sorted.push({ id: shf.heading, desc: shf.desc });
+ return sorted;
+ }, [] as SortingRule[]);
+ }
+
+ @action
+ changeSorting = (col: any) => {
+ console.log(col.heading);
+ if (col.desc === undefined) {
+ // no sorting
+ this.props.changeColumnSort(col, true);
+ } else if (col.desc === true) {
+ // descending sort
+ this.props.changeColumnSort(col, false);
+ } else if (col.desc === false) {
+ // ascending sort
+ this.props.changeColumnSort(col, undefined);
+ }
+ }
+
+ @action
+ changeTitleMode = () => { console.log("header clicked"); this._showTitleDropdown = !this._showTitleDropdown; }
+
+ @computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); }
+ @computed get tableColumns(): Column<Doc>[] {
+
+ const possibleKeys = this.props.documentKeys.filter(key => this.props.columns.findIndex(existingKey => existingKey.heading.toUpperCase() === key.toUpperCase()) === -1);
+ const columns: Column<Doc>[] = [];
+ const tableIsFocused = this.props.isFocused(this.props.Document);
+ const focusedRow = this._focusedCell.row;
+ const focusedCol = this._focusedCell.col;
+ const isEditable = !this.props.headerIsEditing;
+
+ if (this.childDocs.reduce((found, doc) => found || doc.type === "collection", false)) {
+ columns.push(
+ {
+ expander: true,
+ Header: "",
+ width: 30,
+ Expander: (rowInfo) => {
+ if (rowInfo.original.type === "collection") {
+ if (rowInfo.isExpanded) return <div className="collectionSchemaView-expander" onClick={() => this.onCloseCollection(rowInfo.original)}><FontAwesomeIcon icon={"sort-up"} size="sm" /></div>;
+ if (!rowInfo.isExpanded) return <div className="collectionSchemaView-expander" onClick={() => this.onExpandCollection(rowInfo.original)}><FontAwesomeIcon icon={"sort-down"} size="sm" /></div>;
+ } else {
+ return null;
+ }
+ }
+ }
+ );
+ }
+
+ const cols = this.props.columns.map(col => {
+
+ const keysDropdown = <KeysDropdown
+ keyValue={col.heading}
+ possibleKeys={possibleKeys}
+ existingKeys={this.props.columns.map(c => c.heading)}
+ canAddNew={true}
+ addNew={false}
+ onSelect={this.props.changeColumns}
+ setIsEditing={this.props.setHeaderIsEditing}
+
+ // try commenting this out
+ width={"100%"}
+ />;
+
+ const icon: IconProp = this.getColumnType(col) === ColumnType.Number ? "hashtag" : this.getColumnType(col) === ColumnType.String ? "font" :
+ this.getColumnType(col) === ColumnType.Boolean ? "check-square" : this.getColumnType(col) === ColumnType.Doc ? "file" :
+ this.getColumnType(col) === ColumnType.Image ? "image" : this.getColumnType(col) === ColumnType.List ? "list-ul" :
+ this.getColumnType(col) === ColumnType.Date ? "calendar" : "align-justify";
+
+ const headerText = this._showTitleDropdown ? keysDropdown : <div
+ onClick={this.changeTitleMode}
+ style={{
+ background: col.color, padding: "2px",
+ letterSpacing: "2px",
+ textTransform: "uppercase",
+ display: "flex"
+ }}>
+ {col.heading}</div>;
+
+ const sortIcon = col.desc === undefined ? "circle" : col.desc === true ? "caret-down" : "caret-up";
+
+ const header =
+ <div //className="collectionSchemaView-header"
+ //onClick={e => this.props.openHeader(col, menuContent, e.clientX, e.clientY)}
+ className="collectionSchemaView-menuOptions-wrapper"
+ style={{
+ background: col.color, padding: "2px",
+ display: "flex"
+ }}>
+ <FontAwesomeIcon icon={icon} size="lg" style={{ display: "inline", paddingLeft: "7px" }} />
+ <div className="keys-dropdown"
+ style={{ display: "inline", zIndex: 1000 }}>
+ {keysDropdown}
+ </div>
+ <div onClick={e => this.changeSorting(col)}
+ style={{ paddingRight: "6px", display: "inline" }}>
+ <FontAwesomeIcon icon={sortIcon} size="sm" />
+ </div>
+ <div onClick={e => this.props.openHeader(col, e.clientX, e.clientY)}
+ style={{ float: "right", paddingRight: "6px" }}>
+ <FontAwesomeIcon icon={"compass"} size="sm" />
+ </div>
+ </div>;
+
+ return {
+ Header: <MovableColumn columnRenderer={header} columnValue={col} allColumns={this.props.columns} reorderColumns={this.props.reorderColumns} ScreenToLocalTransform={this.props.ScreenToLocalTransform} />,
+ accessor: (doc: Doc) => doc ? doc[col.heading] : 0,
+ id: col.heading,
+ Cell: (rowProps: CellInfo) => {
+ const rowIndex = rowProps.index;
+ const columnIndex = this.props.columns.map(c => c.heading).indexOf(rowProps.column.id!);
+ const isFocused = focusedRow === rowIndex && focusedCol === columnIndex && tableIsFocused;
+
+ const props: CellProps = {
+ row: rowIndex,
+ col: columnIndex,
+ rowProps: rowProps,
+ isFocused: isFocused,
+ changeFocusedCellByIndex: this.changeFocusedCellByIndex,
+ CollectionView: this.props.CollectionView,
+ ContainingCollection: this.props.ContainingCollectionView,
+ Document: this.props.Document,
+ fieldKey: this.props.fieldKey,
+ renderDepth: this.props.renderDepth,
+ addDocTab: this.props.addDocTab,
+ pinToPres: this.props.pinToPres,
+ moveDocument: this.props.moveDocument,
+ setIsEditing: this.setCellIsEditing,
+ isEditable: isEditable,
+ setPreviewDoc: this.props.setPreviewDoc,
+ setComputed: this.setComputed,
+ getField: this.getField,
+ showDoc: this.showDoc,
+ };
+
+ const colType = this.getColumnType(col);
+ if (colType === ColumnType.Number) return <CollectionSchemaNumberCell {...props} />;
+ if (colType === ColumnType.String) return <CollectionSchemaStringCell {...props} />;
+ if (colType === ColumnType.Boolean) return <CollectionSchemaCheckboxCell {...props} />;
+ if (colType === ColumnType.Doc) return <CollectionSchemaDocCell {...props} />;
+ if (colType === ColumnType.Image) return <CollectionSchemaImageCell {...props} />;
+ if (colType === ColumnType.List) return <CollectionSchemaListCell {...props} />;
+ if (colType === ColumnType.Date) return <CollectionSchemaDateCell {...props} />;
+ return <CollectionSchemaCell {...props} />;
+ },
+ minWidth: 200,
+ };
+ });
+ columns.push(...cols);
+
+ columns.push({
+ Header: <CollectionSchemaAddColumnHeader createColumn={this.createColumn} />,
+ accessor: (doc: Doc) => 0,
+ id: "add",
+ Cell: (rowProps: CellInfo) => <></>,
+ width: 28,
+ resizable: false
+ });
+ return columns;
+ }
+
+ constructor(props: SchemaTableProps) {
+ super(props);
+ // convert old schema columns (list of strings) into new schema columns (list of schema header fields)
+ const oldSchemaHeaders = Cast(this.props.Document._schemaHeaders, listSpec("string"), []);
+ if (oldSchemaHeaders?.length && typeof oldSchemaHeaders[0] !== "object") {
+ const newSchemaHeaders = oldSchemaHeaders.map(i => typeof i === "string" ? new SchemaHeaderField(i, "#f1efeb") : i);
+ this.props.Document._schemaHeaders = new List<SchemaHeaderField>(newSchemaHeaders);
+ } else if (this.props.Document._schemaHeaders === undefined) {
+ this.props.Document._schemaHeaders = new List<SchemaHeaderField>([new SchemaHeaderField("title", "#f1efeb")]);
+ }
+ }
+
+ componentDidMount() {
+ document.addEventListener("keydown", this.onKeyDown);
+ }
+
+ componentWillUnmount() {
+ document.removeEventListener("keydown", this.onKeyDown);
+ }
+
+ tableAddDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => {
+ return Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before);
+ }
+
+ private getTrProps: ComponentPropsGetterR = (state, rowInfo) => {
+ return !rowInfo ? {} : {
+ ScreenToLocalTransform: this.props.ScreenToLocalTransform,
+ addDoc: this.tableAddDoc,
+ removeDoc: this.props.deleteDocument,
+ rowInfo,
+ rowFocused: !this.props.headerIsEditing && rowInfo.index === this._focusedCell.row && this.props.isFocused(this.props.Document),
+ textWrapRow: this.toggleTextWrapRow,
+ rowWrapped: this.textWrappedRows.findIndex(id => rowInfo.original[Id] === id) > -1,
+ dropAction: StrCast(this.props.Document.childDropAction),
+ addDocTab: this.props.addDocTab
+ };
+ }
+
+ private getTdProps: ComponentPropsGetterR = (state, rowInfo, column, instance) => {
+ if (!rowInfo || column) return {};
+
+ const row = rowInfo.index;
+ //@ts-ignore
+ const col = this.columns.map(c => c.heading).indexOf(column!.id);
+ const isFocused = this._focusedCell.row === row && this._focusedCell.col === col && this.props.isFocused(this.props.Document);
+ // TODO: editing border doesn't work :(
+ return {
+ style: {
+ border: !this.props.headerIsEditing && isFocused ? "2px solid rgb(255, 160, 160)" : "1px solid #f1efeb"
+ }
+ };
+ }
+
+ @action
+ onCloseCollection = (collection: Doc): void => {
+ const index = this._openCollections.findIndex(col => col === collection[Id]);
+ if (index > -1) this._openCollections.splice(index, 1);
+ }
+
+ @action onExpandCollection = (collection: Doc) => this._openCollections.push(collection[Id]);
+ @action setCellIsEditing = (isEditing: boolean) => this._cellIsEditing = isEditing;
+
+ @action
+ onKeyDown = (e: KeyboardEvent): void => {
+ if (!this._cellIsEditing && !this.props.headerIsEditing && this.props.isFocused(this.props.Document)) {// && this.props.isSelected(true)) {
+ const direction = e.key === "Tab" ? "tab" : e.which === 39 ? "right" : e.which === 37 ? "left" : e.which === 38 ? "up" : e.which === 40 ? "down" : "";
+ this._focusedCell = this.changeFocusedCellByDirection(direction, this._focusedCell.row, this._focusedCell.col);
+
+ const pdoc = FieldValue(this.childDocs[this._focusedCell.row]);
+ pdoc && this.props.setPreviewDoc(pdoc);
+ }
+ }
+
+ changeFocusedCellByDirection = (direction: string, curRow: number, curCol: number) => {
+ switch (direction) {
+ case "tab": return { row: (curRow + 1 === this.childDocs.length ? 0 : curRow + 1), col: curCol + 1 === this.props.columns.length ? 0 : curCol + 1 };
+ case "right": return { row: curRow, col: curCol + 1 === this.props.columns.length ? curCol : curCol + 1 };
+ case "left": return { row: curRow, col: curCol === 0 ? curCol : curCol - 1 };
+ case "up": return { row: curRow === 0 ? curRow : curRow - 1, col: curCol };
+ case "down": return { row: curRow + 1 === this.childDocs.length ? curRow : curRow + 1, col: curCol };
+ }
+ return this._focusedCell;
+ }
+
+ @action
+ changeFocusedCellByIndex = (row: number, col: number): void => {
+ if (this._focusedCell.row !== row || this._focusedCell.col !== col) {
+ this._focusedCell = { row: row, col: col };
+ }
+ this.props.setFocused(this.props.Document);
+ }
+
+ @undoBatch
+ createRow = () => {
+ this.props.addDocument(Docs.Create.TextDocument("", { title: "", _width: 100, _height: 30 }));
+ }
+
+ @undoBatch
+ @action
+ createColumn = () => {
+ let index = 0;
+ let found = this.props.columns.findIndex(col => col.heading.toUpperCase() === "New field".toUpperCase()) > -1;
+ while (found) {
+ index++;
+ found = this.props.columns.findIndex(col => col.heading.toUpperCase() === ("New field (" + index + ")").toUpperCase()) > -1;
+ }
+ this.props.columns.push(new SchemaHeaderField(`New field ${index ? "(" + index + ")" : ""}`, "#f1efeb"));
+ }
+
+ @action
+ getColumnType = (column: SchemaHeaderField): ColumnType => {
+ // added functionality to convert old column type stuff to new column type stuff -syip
+ if (column.type && column.type !== 0) {
+ return column.type;
+ }
+ if (columnTypes.get(column.heading)) {
+ column.type = columnTypes.get(column.heading)!;
+ return columnTypes.get(column.heading)!;
+ }
+ const typesDoc = FieldValue(Cast(this.props.Document.schemaColumnTypes, Doc));
+ if (!typesDoc) {
+ column.type = ColumnType.Any;
+ return ColumnType.Any;
+ }
+ column.type = NumCast(typesDoc[column.heading]);
+ return NumCast(typesDoc[column.heading]);
+ }
+
+ @undoBatch
+ @action
+ toggleTextwrap = async () => {
+ const textwrappedRows = Cast(this.props.Document.textwrappedSchemaRows, listSpec("string"), []);
+ if (textwrappedRows.length) {
+ this.props.Document.textwrappedSchemaRows = new List<string>([]);
+ } else {
+ const docs = DocListCast(this.props.Document[this.props.fieldKey]);
+ const allRows = docs instanceof Doc ? [docs[Id]] : docs.map(doc => doc[Id]);
+ this.props.Document.textwrappedSchemaRows = new List<string>(allRows);
+ }
+ }
+
+ @action
+ toggleTextWrapRow = (doc: Doc): void => {
+ const textWrapped = this.textWrappedRows;
+ const index = textWrapped.findIndex(id => doc[Id] === id);
+
+ index > -1 ? textWrapped.splice(index, 1) : textWrapped.push(doc[Id]);
+
+ this.textWrappedRows = textWrapped;
+ }
+
+ @computed
+ get reactTable() {
+ const children = this.childDocs;
+ const hasCollectionChild = children.reduce((found, doc) => found || doc.type === "collection", false);
+ const expandedRowsList = this._openCollections.map(col => children.findIndex(doc => doc[Id] === col).toString());
+ const expanded = {};
+ //@ts-ignore
+ expandedRowsList.forEach(row => expanded[row] = true);
+ const rerender = [...this.textWrappedRows]; // TODO: get component to rerender on text wrap change without needign to console.log :((((
+
+ return <ReactTable
+ style={{ position: "relative" }}
+ data={children}
+ page={0}
+ pageSize={children.length}
+ showPagination={false}
+ columns={this.tableColumns}
+ getTrProps={this.getTrProps}
+ getTdProps={this.getTdProps}
+ sortable={false}
+ TrComponent={MovableRow}
+ sorted={this.sorted}
+ expanded={expanded}
+ resized={this.resized}
+ onResizedChange={this.props.onResizedChange}
+ SubComponent={!hasCollectionChild ? undefined : row => (row.original.type !== "collection") ? (null) :
+ <div className="reactTable-sub"><SchemaTable {...this.props} Document={row.original} dataDoc={undefined} childDocs={undefined} /></div>}
+
+ />;
+ }
+
+ onContextMenu = (e: React.MouseEvent): void => {
+ if (!e.isPropagationStopped() && this.props.Document[Id] !== "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
+ // ContextMenu.Instance.addItem({ description: "Make DB", event: this.makeDB, icon: "table" });
+ ContextMenu.Instance.addItem({ description: "Toggle text wrapping", event: this.toggleTextwrap, icon: "table" });
+ }
+ }
+
+ getField = (row: number, col?: number) => {
+ const docs = this.childDocs;
+
+ row = row % docs.length;
+ while (row < 0) row += docs.length;
+ const columns = this.props.columns;
+ const doc = docs[row];
+ if (col === undefined) {
+ return doc;
+ }
+ if (col >= 0 && col < columns.length) {
+ const column = this.props.columns[col].heading;
+ return doc[column];
+ }
+ return undefined;
+ }
+
+ createTransformer = (row: number, col: number): Transformer => {
+ const self = this;
+ const captures: { [name: string]: Field } = {};
+
+ const transformer: ts.TransformerFactory<ts.SourceFile> = context => {
+ return root => {
+ function visit(node: ts.Node) {
+ node = ts.visitEachChild(node, visit, context);
+ if (ts.isIdentifier(node)) {
+ const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node;
+ const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node;
+ if (isntPropAccess && isntPropAssign) {
+ if (node.text === "$r") {
+ return ts.createNumericLiteral(row.toString());
+ } else if (node.text === "$c") {
+ return ts.createNumericLiteral(col.toString());
+ } else if (node.text === "$") {
+ if (ts.isCallExpression(node.parent)) {
+ // captures.doc = self.props.Document;
+ // captures.key = self.props.fieldKey;
+ }
+ }
+ }
+ }
+
+ return node;
+ }
+ return ts.visitNode(root, visit);
+ };
+ };
+
+ // const getVars = () => {
+ // return { capturedVariables: captures };
+ // };
+
+ return { transformer, /*getVars*/ };
+ }
+
+ setComputed = (script: string, doc: Doc, field: string, row: number, col: number): boolean => {
+ script =
+ `const $ = (row:number, col?:number) => {
+ if(col === undefined) {
+ return (doc as any)[key][row + ${row}];
+ }
+ return (doc as any)[key][row + ${row}][(doc as any)._schemaHeaders[col + ${col}].heading];
+ }
+ return ${script}`;
+ const compiled = CompileScript(script, { params: { this: Doc.name }, capturedVariables: { doc: this.props.Document, key: this.props.fieldKey }, typecheck: false, transformer: this.createTransformer(row, col) });
+ if (compiled.compiled) {
+ doc[field] = new ComputedField(compiled);
+ return true;
+ }
+ return false;
+ }
+
+ @action
+ showDoc = (doc: Doc | undefined, dataDoc?: Doc, screenX?: number, screenY?: number) => {
+ this._showDoc = doc;
+ if (dataDoc && screenX && screenY) {
+ this._showDocPos = this.props.ScreenToLocalTransform().transformPoint(screenX, screenY);
+ }
+ }
+
+ onOpenClick = () => {
+ if (this._showDoc) {
+ this.props.addDocTab(this._showDoc, "onRight");
+ }
+ }
+
+ getPreviewTransform = (): Transform => {
+ return this.props.ScreenToLocalTransform().translate(- this.borderWidth - 4 - this.tableWidth, - this.borderWidth);
+ }
+
+ render() {
+ const preview = "";
+ return <div className="collectionSchemaView-table" onPointerDown={this.props.onPointerDown} onWheel={e => this.props.active(true) && e.stopPropagation()}
+ onDrop={e => this.props.onDrop(e, {})} onContextMenu={this.onContextMenu} >
+ {this.reactTable}
+ <div className="collectionSchemaView-addRow" onClick={() => this.createRow()}>+ new</div>
+ {!this._showDoc ? (null) :
+ <div className="collectionSchemaView-documentPreview" //onClick={() => { this.onOpenClick(); }}
+ style={{
+ position: "absolute", width: 150, height: 150,
+ background: "dimGray", display: "block", top: 0, left: 0,
+ transform: `translate(${this._showDocPos[0]}px, ${this._showDocPos[1] - 180}px)`
+ }}
+ ref="overlay"><ContentFittingDocumentView
+ Document={this._showDoc}
+ DataDoc={this._showDataDoc}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
+ fitToBox={true}
+ FreezeDimensions={true}
+ focus={emptyFunction}
+ LibraryPath={emptyPath}
+ renderDepth={this.props.renderDepth}
+ rootSelected={() => false}
+ PanelWidth={() => 150}
+ PanelHeight={() => 150}
+ ScreenToLocalTransform={this.getPreviewTransform}
+ docFilters={returnEmptyFilter}
+ ContainingCollectionDoc={this.props.CollectionView?.props.Document}
+ ContainingCollectionView={this.props.CollectionView}
+ moveDocument={this.props.moveDocument}
+ parentActive={this.props.active}
+ whenActiveChanged={emptyFunction}
+ addDocTab={this.props.addDocTab}
+ pinToPres={this.props.pinToPres}
+ bringToFront={returnFalse}
+ ContentScaling={returnOne}>
+ </ContentFittingDocumentView>
+ </div>}
+ </div>;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index e2d4d4459..9069097e6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,53 +1,51 @@
import { library } from "@fortawesome/fontawesome-svg-core";
-import { faEye, faEyeSlash } from "@fortawesome/free-regular-svg-icons";
+import { faEye } from "@fortawesome/free-regular-svg-icons";
import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, _allowStateChangesInsideComputed, trace } from "mobx";
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
import { computedFn } from "mobx-utils";
-import { Doc, HeightSym, Opt, WidthSym, DocListCast } from "../../../../fields/Doc";
-import { documentSchema, collectionSchema } from "../../../../fields/documentSchemas";
+import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../../fields/Doc";
+import { collectionSchema, documentSchema } from "../../../../fields/documentSchemas";
import { Id } from "../../../../fields/FieldSymbols";
-import { InkData, InkField, InkTool, PointData } from "../../../../fields/InkField";
+import { InkData, InkField, InkTool } from "../../../../fields/InkField";
import { List } from "../../../../fields/List";
import { RichTextField } from "../../../../fields/RichTextField";
-import { createSchema, listSpec, makeInterface } from "../../../../fields/Schema";
-import { ScriptField, ComputedField } from "../../../../fields/ScriptField";
+import { createSchema, makeInterface } from "../../../../fields/Schema";
+import { ScriptField } from "../../../../fields/ScriptField";
import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types";
import { TraceMobx } from "../../../../fields/util";
import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
-import { aggregateBounds, intersectRect, returnOne, Utils, returnZero, returnFalse, numberRange } from "../../../../Utils";
+import { aggregateBounds, intersectRect, returnFalse, returnOne, returnZero, Utils } from "../../../../Utils";
import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
import { DocServer } from "../../../DocServer";
import { Docs, DocUtils } from "../../../documents/Documents";
+import { DocumentType } from "../../../documents/DocumentTypes";
import { DocumentManager } from "../../../util/DocumentManager";
import { DragManager, dropActionType } from "../../../util/DragManager";
import { HistoryUtil } from "../../../util/History";
import { InteractionUtils } from "../../../util/InteractionUtils";
import { SelectionManager } from "../../../util/SelectionManager";
+import { SnappingManager } from "../../../util/SnappingManager";
import { Transform } from "../../../util/Transform";
import { undoBatch, UndoManager } from "../../../util/UndoManager";
import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss";
+import { Timeline } from "../../animationtimeline/Timeline";
import { ContextMenu } from "../../ContextMenu";
-import { ContextMenuProps } from "../../ContextMenuItem";
+import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth } from "../../InkingStroke";
import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
-import { DocumentViewProps, DocumentView } from "../../nodes/DocumentView";
+import { DocumentLinksButton } from "../../nodes/DocumentLinksButton";
+import { DocumentViewProps } from "../../nodes/DocumentView";
import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox";
import { pageSchema } from "../../nodes/ImageBox";
-import PDFMenu from "../../pdf/PDFMenu";
import { CollectionDockingView } from "../CollectionDockingView";
import { CollectionSubView } from "../CollectionSubView";
-import { computePivotLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult, computerStarburstLayout, computerPassLayout } from "./CollectionFreeFormLayoutEngines";
+import { CollectionViewType } from "../CollectionView";
+import { computePivotLayout, computerPassLayout, computerStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from "./CollectionFreeFormLayoutEngines";
import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
import "./CollectionFreeFormView.scss";
import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
import { MarqueeView } from "./MarqueeView";
import React = require("react");
-import { CollectionViewType } from "../CollectionView";
-import { Timeline } from "../../animationtimeline/Timeline";
-import { SnappingManager } from "../../../util/SnappingManager";
-import { InkingStroke, ActiveArrowStart, ActiveArrowEnd, ActiveInkColor, ActiveFillColor, ActiveInkWidth, ActiveInkBezierApprox, ActiveDash } from "../../InkingStroke";
-import { DocumentType } from "../../../documents/DocumentTypes";
-import { DocumentLinksButton } from "../../nodes/DocumentLinksButton";
library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
@@ -602,7 +600,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
pan = (e: PointerEvent | React.Touch | { clientX: number, clientY: number }): void => {
// bcz: theres should be a better way of doing these than referencing these static instances directly
MarqueeOptionsMenu.Instance?.fadeOut(true);// I think it makes sense for the marquee menu to go away when panned. -syip2
- PDFMenu.Instance.fadeOut(true);
const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
this.setPan((this.Document._panX || 0) - dx, (this.Document._panY || 0) - dy, undefined, true);
@@ -870,7 +867,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
focusDocument = (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: () => boolean) => {
const state = HistoryUtil.getState();
- // TODO This technically isn't correct if type !== "doc", as
+ // TODO This technically isn't correct if type !== "doc", as
// currently nothing is done, but we should probably push a new state
if (state.type === "doc" && this.Document._panX !== undefined && this.Document._panY !== undefined) {
const init = state.initializers![this.Document[Id]];
@@ -1205,7 +1202,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
private thumbIdentifier?: number;
onContextMenu = (e: React.MouseEvent) => {
- if (this.props.annotationsKey) return;
+ if (this.props.annotationsKey || !ContextMenu.Instance) return;
const appearance = ContextMenu.Instance.findByDescription("Appearance...");
const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : [];
@@ -1454,4 +1451,4 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
{this.props.children()}
</div>;
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss
index a9fab4c1e..753de6bef 100644
--- a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss
+++ b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss
@@ -9,7 +9,6 @@
height: 100%;
}
-
}
.sketch-picker {
@@ -36,6 +35,44 @@
background: #323232;
display: block;
+ }
+}
+
+@media only screen and (max-device-width: 480px) {
+ .antimodeMenu-button {
+ font-size: 50%;
+
+ .color-preview {
+ width: 100%;
+ height: 100%;
+ }
+
+ }
+
+ .sketch-picker {
+ background: #323232;
+
+ .flexbox-fit {
+ background: #323232;
+ }
+ }
+
+ .btn-group {
+ display: grid;
+ grid-template-columns: auto auto;
+ /* Make the buttons appear below each other */
+ }
+
+ .btn2-group {
+ display: block;
+ background: #323232;
+ grid-template-columns: auto;
+ /* Make the buttons appear below each other */
+ .antimodeMenu-button {
+ background: #323232;
+ display: block;
+ font-size: 50%;
+ }
}
} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
index f1032adaa..f47fca6ac 100644
--- a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
@@ -20,6 +20,8 @@ import { faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSubscr
library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, faCaretDown, faPalette, faArrowsAlt, faHighlighter, faLink, faPaintRoller, faBars, faFillDrip, faBrush, faPenNib, faShapes, faArrowLeft, faEllipsisH, faBezierCurve);
+
+
@observer
export default class InkOptionsMenu extends AntimodeMenu {
static Instance: InkOptionsMenu;
@@ -42,6 +44,8 @@ export default class InkOptionsMenu extends AntimodeMenu {
@observable _dashBtn = false;
@observable _shapeBtn = false;
+
+
constructor(props: Readonly<{}>) {
super(props);
InkOptionsMenu.Instance = this;
@@ -325,4 +329,4 @@ Scripting.addGlobal(function activatePen(penBtn: any) {
Doc.SetSelectedTool(InkTool.None);
InkOptionsMenu.Instance.fadeOut(true);
}
-}); \ No newline at end of file
+});
diff --git a/src/client/views/linking/LinkMenu.scss b/src/client/views/linking/LinkMenu.scss
index c372e7098..6468ccd3d 100644
--- a/src/client/views/linking/LinkMenu.scss
+++ b/src/client/views/linking/LinkMenu.scss
@@ -15,7 +15,7 @@
}
.linkMenu-group {
- border-bottom: 0.5px solid lightgray;
+ border-bottom: 0.5px solid lightgray;
padding: 5px 0;
@@ -30,9 +30,11 @@
p {
background-color: lightgray;
}
+
p.expand-one {
width: calc(100% - 26px);
}
+
.linkEditor-tableButton {
display: block;
}
@@ -50,7 +52,4 @@
display: none;
}
}
-}
-
-
-
+} \ No newline at end of file
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index 0fcc0f0b9..c672511ac 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -10,6 +10,7 @@ import { LinkMenuGroup } from "./LinkMenuGroup";
import { faTrash } from '@fortawesome/free-solid-svg-icons';
import { library } from "@fortawesome/fontawesome-svg-core";
import { DocumentLinksButton } from "../nodes/DocumentLinksButton";
+import { LinkDocPreview } from "../nodes/LinkDocPreview";
library.add(faTrash);
@@ -28,7 +29,14 @@ export class LinkMenu extends React.Component<Props> {
@action
onClick = (e: PointerEvent) => {
- if (!Array.from(this._linkMenuRef?.getElementsByTagName((e.target as HTMLElement).tagName) || []).includes(e.target as any)) {
+
+ LinkDocPreview.LinkInfo = undefined;
+
+ if (this._linkMenuRef?.contains(e.target as any)) {
+ DocumentLinksButton.EditLink = undefined;
+ }
+
+ if (this._linkMenuRef && !this._linkMenuRef.contains(e.target as any)) {
DocumentLinksButton.EditLink = undefined;
}
}
@@ -70,7 +78,8 @@ export class LinkMenu extends React.Component<Props> {
render() {
const sourceDoc = this.props.docView.props.Document;
const groups: Map<string, Doc[]> = LinkManager.Instance.getRelatedGroupedLinks(sourceDoc);
- return <div className="linkMenu-list" ref={(r) => this._linkMenuRef = r} style={{ left: this.props.location[0], top: this.props.location[1] }}>
+ return <div className="linkMenu-list"
+ ref={(r) => this._linkMenuRef = r} style={{ left: this.props.location[0], top: this.props.location[1] }}>
{!this._editingLink ?
this.renderAllGroups(groups) :
<LinkEditor sourceDoc={this.props.docView.props.Document} linkDoc={this._editingLink} showLinks={action(() => this._editingLink = undefined)} />
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index 89deb3a55..7892d381b 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -80,11 +80,11 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
return (
<div className="linkMenu-group">
- <div className="linkMenu-group-name">
+ {/* <div className="linkMenu-group-name">
<p ref={this._drag} onPointerDown={this.onLinkButtonDown}
className={this.props.groupType === "*" || this.props.groupType === "" ? "" : "expand-one"} > {this.props.groupType}:</p>
{this.props.groupType === "*" || this.props.groupType === "" ? <></> : this.viewGroupAsTable(this.props.groupType)}
- </div>
+ </div> */}
<div className="linkMenu-group-wrapper">
{groupItems}
</div>
diff --git a/src/client/views/linking/LinkMenuItem.scss b/src/client/views/linking/LinkMenuItem.scss
index fd0954f65..e3ce69cd7 100644
--- a/src/client/views/linking/LinkMenuItem.scss
+++ b/src/client/views/linking/LinkMenuItem.scss
@@ -5,7 +5,7 @@
position: relative;
display: flex;
font-size: 12px;
-
+
.linkMenu-name {
position: relative;
@@ -22,7 +22,7 @@
.linkMenu-item-content {
width: 100%;
}
-
+
.link-metadata {
padding: 0 10px 0 16px;
margin-bottom: 4px;
@@ -35,11 +35,13 @@
.linkMenu-item-buttons {
display: flex;
}
+
.linkMenu-item-content {
&.expand-two p {
width: calc(100% - 52px);
background-color: lightgray;
}
+
&.expand-three p {
width: calc(100% - 84px);
background-color: lightgray;
@@ -49,11 +51,11 @@
}
.linkMenu-item-buttons {
- display: none;
position: absolute;
top: 50%;
right: 0;
transform: translateY(-50%);
+ display: none;
.button {
width: 20px;
@@ -61,7 +63,6 @@
margin: 0;
margin-right: 6px;
border-radius: 50%;
- cursor: pointer;
pointer-events: auto;
background-color: $dark-color;
color: $light-color;
@@ -80,8 +81,10 @@
&:last-child {
margin-right: 0;
}
+
&:hover {
background: $main-accent;
+ cursor: grab;
}
}
} \ No newline at end of file
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index edc18b6a9..04cd83ee0 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -1,5 +1,5 @@
import { library } from '@fortawesome/fontawesome-svg-core';
-import { faArrowRight, faChevronDown, faChevronUp, faEdit, faEye, faTimes } from '@fortawesome/free-solid-svg-icons';
+import { faArrowRight, faChevronDown, faChevronUp, faEdit, faEye, faTimes, faPencilAlt } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, observable } from 'mobx';
import { observer } from "mobx-react";
@@ -13,7 +13,9 @@ import React = require("react");
import { DocumentManager } from '../../util/DocumentManager';
import { setupMoveUpEvents, emptyFunction } from '../../../Utils';
import { DocumentView } from '../nodes/DocumentView';
-library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp);
+import { DocumentLinksButton } from '../nodes/DocumentLinksButton';
+import { LinkDocPreview } from '../nodes/LinkDocPreview';
+library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp, faPencilAlt);
interface LinkMenuItemProps {
@@ -69,6 +71,8 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
private _eleClone: any;
_editRef = React.createRef<HTMLDivElement>();
+ _buttonRef = React.createRef<HTMLDivElement>();
+
@observable private _showMore: boolean = false;
@action toggleShowMore(e: React.PointerEvent) { e.stopPropagation(); this._showMore = !this._showMore; }
@@ -95,6 +99,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
return (<div className="link-metadata">{mdRows}</div>);
}
+ @action
onLinkButtonDown = (e: React.PointerEvent): void => {
this._downX = e.clientX;
this._downY = e.clientY;
@@ -104,6 +109,10 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
document.addEventListener("pointermove", this.onLinkButtonMoved);
document.removeEventListener("pointerup", this.onLinkButtonUp);
document.addEventListener("pointerup", this.onLinkButtonUp);
+
+ if (this._buttonRef && this._buttonRef.current?.contains(e.target as any)) {
+ LinkDocPreview.LinkInfo = undefined;
+ }
}
onLinkButtonUp = (e: PointerEvent): void => {
@@ -124,7 +133,10 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
e.stopPropagation();
}
+ @action
onContextMenu = (e: React.MouseEvent) => {
+ DocumentLinksButton.EditLink = undefined;
+ LinkDocPreview.LinkInfo = undefined;
e.preventDefault();
ContextMenu.Instance.addItem({ description: "Follow Default Link", event: () => this.followDefault(), icon: "arrow-right" });
ContextMenu.Instance.displayMenu(e.clientX, e.clientY);
@@ -132,9 +144,20 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
@action.bound
async followDefault() {
+ console.log("FOLLOWWW");
+ DocumentLinksButton.EditLink = undefined;
+ LinkDocPreview.LinkInfo = undefined;
DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.sourceDoc, doc => this.props.addDocTab(doc, "onRight"), false);
}
+ @action
+ deleteLink = (): void => {
+ LinkManager.Instance.deleteLink(this.props.linkDoc);
+ //this.props.showLinks();
+ LinkDocPreview.LinkInfo = undefined;
+ DocumentLinksButton.EditLink = undefined;
+ }
+
render() {
const keys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);//groupMetadataKeys.get(this.props.groupType);
const canExpand = keys ? keys.length > 0 : false;
@@ -142,13 +165,25 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
return (
<div className="linkMenu-item">
<div className={canExpand ? "linkMenu-item-content expand-three" : "linkMenu-item-content expand-two"}>
- <div ref={this._drag} className="linkMenu-name" title="drag to view target. click to customize." onPointerDown={this.onLinkButtonDown}>
+ <div ref={this._drag} className="linkMenu-name" //title="drag to view target. click to customize."
+ onPointerLeave={action(() => LinkDocPreview.LinkInfo = undefined)}
+ onPointerEnter={action(e => this.props.linkDoc && (LinkDocPreview.LinkInfo = {
+ addDocTab: this.props.addDocTab,
+ linkSrc: this.props.sourceDoc,
+ linkDoc: this.props.linkDoc,
+ Location: [e.clientX, e.clientY + 20]
+ }))}
+ onPointerDown={this.onLinkButtonDown}>
<p >{StrCast(this.props.destinationDoc.title)}</p>
- <div className="linkMenu-item-buttons">
+ <div className="linkMenu-item-buttons" ref={this._buttonRef} >
{canExpand ? <div title="Show more" className="button" onPointerDown={e => this.toggleShowMore(e)}>
<FontAwesomeIcon className="fa-icon" icon={this._showMore ? "chevron-up" : "chevron-down"} size="sm" /></div> : <></>}
- <div title="Edit link" className="button" ref={this._editRef} onPointerDown={this.onEdit}><FontAwesomeIcon className="fa-icon" icon="edit" size="sm" /></div>
- <div title="Follow link" className="button" onClick={this.followDefault} onContextMenu={this.onContextMenu}>
+
+ {/* <div title="Edit link" className="button" ref={this._editRef} onPointerDown={this.onEdit}>
+ <FontAwesomeIcon className="fa-icon" icon="pencil-alt" size="sm" /></div> */}
+ <div title="Delete link" className="button" onPointerDown={this.deleteLink}>
+ <FontAwesomeIcon className="fa-icon" icon="trash" size="sm" /></div>
+ <div title="Follow link" className="button" onPointerDown={this.followDefault} onContextMenu={this.onContextMenu}>
<FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" />
</div>
</div>
diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss
index 53b54d7e4..c959b79f5 100644
--- a/src/client/views/nodes/AudioBox.scss
+++ b/src/client/views/nodes/AudioBox.scss
@@ -1,141 +1,169 @@
-.audiobox-container, .audiobox-container-interactive {
+.audiobox-container,
+.audiobox-container-interactive {
width: 100%;
height: 100%;
position: inherit;
- display:flex;
+ display: flex;
pointer-events: all;
- cursor:default;
+ cursor: default;
+
.audiobox-buttons {
display: flex;
width: 100%;
align-items: center;
}
+
.audiobox-handle {
- width:20px;
- height:100%;
- display:inline-block;
+ width: 20px;
+ height: 100%;
+ display: inline-block;
}
- .audiobox-control, .audiobox-control-interactive {
- top:0;
+
+ .audiobox-control,
+ .audiobox-control-interactive {
+ top: 0;
max-height: 32px;
width: 100%;
- display:inline-block;
+ display: inline-block;
pointer-events: none;
}
+
.audiobox-control-interactive {
pointer-events: all;
}
+
.audiobox-record {
pointer-events: all;
- width:100%;
- height:100%;
+ width: 100%;
+ height: 100%;
position: relative;
pointer-events: none;
}
+
.audiobox-record-interactive {
pointer-events: all;
- width:100%;
- height:100%;
+ width: 100%;
+ height: 100%;
position: relative;
}
+
.audiobox-controls {
- width:100%;
- height:100%;
+ width: 100%;
+ height: 100%;
position: relative;
display: flex;
padding-left: 2px;
+
.audiobox-player {
- margin-top:auto;
- margin-bottom:auto;
- width:100%;
+ margin-top: auto;
+ margin-bottom: auto;
+ width: 100%;
height: 80%;
position: relative;
padding-right: 5px;
display: flex;
- .audiobox-playhead, .audiobox-dictation {
+
+ .audiobox-playhead,
+ .audiobox-dictation {
position: relative;
margin-top: auto;
margin-bottom: auto;
width: 25px;
padding: 2px;
}
+
.audiobox-dictation {
align-items: center;
display: inherit;
background: dimgray;
}
+
.audiobox-timeline {
- position:relative;
- height:100%;
- width:100%;
+ position: relative;
+ height: 100%;
+ width: 100%;
background: white;
border: gray solid 1px;
border-radius: 3px;
+
.audiobox-current {
width: 1px;
- height:100%;
+ height: 100%;
background-color: red;
position: absolute;
}
- .audiobox-linker, .audiobox-linker-mini {
- position:absolute;
- width:15px;
- min-height:10px;
- height:15px;
- margin-left:-2.55px;
- background:gray;
+
+ .audiobox-linker,
+ .audiobox-linker-mini {
+ position: absolute;
+ width: 15px;
+ min-height: 10px;
+ height: 15px;
+ margin-left: -2.55px;
+ background: gray;
border-radius: 100%;
- opacity:0.9;
+ opacity: 0.9;
background-color: transparent;
box-shadow: black 2px 2px 1px;
+
.linkAnchorBox-cont {
position: relative !important;
- height: 100% !important;
+ height: 100% !important;
width: 100% !important;
- left:unset !important;
- top:unset !important;
+ left: unset !important;
+ top: unset !important;
}
}
+
.audiobox-linker-mini {
- width:8px;
- min-height:8px;
- height:8px;
+ width: 8px;
+ min-height: 8px;
+ height: 8px;
box-shadow: black 1px 1px 1px;
margin-left: -1;
margin-top: -2;
+
.linkAnchorBox-cont {
position: relative !important;
- height: 100% !important;
+ height: 100% !important;
width: 100% !important;
- left:unset !important;
- top:unset !important;
+ left: unset !important;
+ top: unset !important;
}
}
- .audiobox-linker:hover, .audiobox-linker-mini:hover {
- opacity:1;
+
+ .audiobox-linker:hover,
+ .audiobox-linker-mini:hover {
+ opacity: 1;
}
- .audiobox-marker-container, .audiobox-marker-minicontainer {
- position:absolute;
- width:10px;
- height:90%;
- top:2.5%;
- background:gray;
+
+ .audiobox-marker-container,
+ .audiobox-marker-minicontainer {
+ position: absolute;
+ width: 10px;
+ height: 90%;
+ top: 2.5%;
+ background: gray;
border-radius: 5px;
box-shadow: black 2px 2px 1px;
+
.audiobox-marker {
- position:relative;
+ position: relative;
height: calc(100% - 15px);
margin-top: 15px;
}
+
.audio-marker:hover {
border: orange 2px solid;
}
}
+
.audiobox-marker-minicontainer {
- width:5px;
+ width: 5px;
border-radius: 1px;
+
.audiobox-marker {
- position:relative;
+ position: relative;
height: calc(100% - 8px);
margin-top: 8px;
}
@@ -143,4 +171,27 @@
}
}
}
+}
+
+
+@media only screen and (max-device-width: 480px) {
+ .audiobox-dictation {
+ font-size: 5em;
+ display: flex;
+ width: 100;
+ justify-content: center;
+ flex-direction: column;
+ align-items: center;
+ }
+
+ .audiobox-container .audiobox-record,
+ .audiobox-container-interactive .audiobox-record {
+ font-size: 3em;
+ }
+
+ .audiobox-container .audiobox-controls .audiobox-player .audiobox-playhead,
+ .audiobox-container .audiobox-controls .audiobox-player .audiobox-dictation,
+ .audiobox-container-interactive .audiobox-controls .audiobox-player .audiobox-playhead {
+ width: 70px;
+ }
} \ No newline at end of file
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 1a935d9b0..d5288fff6 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -161,7 +161,7 @@ export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument
const funcs: ContextMenuProps[] = [];
funcs.push({ description: (this.layoutDoc.playOnSelect ? "Don't play" : "Play") + " when document selected", event: () => this.layoutDoc.playOnSelect = !this.layoutDoc.playOnSelect, icon: "expand-arrows-alt" });
- ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
+ ContextMenu.Instance?.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
}
stopRecording = action(() => {
@@ -193,7 +193,7 @@ export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument
const newDoc = Docs.Create.TextDocument("", {
title: "", _chromeStatus: "disabled",
x: NumCast(this.props.Document.x), y: NumCast(this.props.Document.y) + NumCast(this.props.Document._height) + 10,
- _width: NumCast(this.props.Document._width), _height: 3 * NumCast(this.props.Document._height)
+ _width: NumCast(this.props.Document._width), _height: 2 * NumCast(this.props.Document._height)
});
Doc.GetProto(newDoc).recordingSource = this.dataDoc;
Doc.GetProto(newDoc).recordingStart = ComputedField.MakeFunction(`self.recordingSource["${this.props.fieldKey}-recordingStart"]`);
@@ -228,7 +228,7 @@ export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument
{!this.path ?
<div className="audiobox-buttons">
<div className="audiobox-dictation" onClick={this.onFile}>
- <FontAwesomeIcon style={{ width: "30px", background: this.layoutDoc.playOnSelect ? "yellow" : "dimGray" }} icon="file-alt" size={this.props.PanelHeight() < 36 ? "1x" : "2x"} />
+ <FontAwesomeIcon style={{ width: "30px", background: this.layoutDoc.playOnSelect ? "yellow" : "rgba(0,0,0,0)" }} icon="file-alt" size={this.props.PanelHeight() < 36 ? "1x" : "2x"} />
</div>
<button className={`audiobox-record${interactive}`} style={{ backgroundColor: this.audioState === "recording" ? "red" : "black" }}>
{this.audioState === "recording" ? "STOP" : "RECORD"}
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index 137b387c0..b3a9b6fee 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -71,4 +71,4 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps, ColorDocument
</div>
</div>;
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/ComparisonBox.scss b/src/client/views/nodes/ComparisonBox.scss
index 810a824cf..acf6b1636 100644
--- a/src/client/views/nodes/ComparisonBox.scss
+++ b/src/client/views/nodes/ComparisonBox.scss
@@ -3,7 +3,7 @@
border-radius: inherit;
width: 100%;
height: 100%;
- position: absolute;
+ position: relative;
z-index: 0;
pointer-events: none;
diff --git a/src/client/views/nodes/DocumentLinksButton.scss b/src/client/views/nodes/DocumentLinksButton.scss
index 484f8c469..b58603978 100644
--- a/src/client/views/nodes/DocumentLinksButton.scss
+++ b/src/client/views/nodes/DocumentLinksButton.scss
@@ -23,7 +23,7 @@
&:hover {
background: deepskyblue;
transform: scale(1.05);
- cursor: default;
+ cursor: grab;
}
}
.documentLinksButton {
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 4f4f12521..bfd860f65 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -10,6 +10,7 @@ import React = require("react");
import { DocUtils } from "../../documents/Documents";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { LinkDocPreview } from "./LinkDocPreview";
+import { LinkCreatedBox } from "./LinkCreatedBox";
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -18,11 +19,14 @@ interface DocumentLinksButtonProps {
View: DocumentView;
Offset?: number[];
AlwaysOn?: boolean;
+ InMenu?: boolean;
}
@observer
export class DocumentLinksButton extends React.Component<DocumentLinksButtonProps, {}> {
private _linkButton = React.createRef<HTMLDivElement>();
+ @observable public static StartLink: DocumentView | undefined;
+
@action
onLinkButtonMoved = (e: PointerEvent) => {
if (this._linkButton.current !== null) {
@@ -50,30 +54,73 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
return false;
}
- @observable static StartLink: DocumentView | undefined;
onLinkButtonDown = (e: React.PointerEvent): void => {
setupMoveUpEvents(this, e, this.onLinkButtonMoved, emptyFunction, action((e, doubleTap) => {
- if (doubleTap) {
+ if (doubleTap && this.props.InMenu) {
+ //action(() => Doc.BrushDoc(this.props.View.Document));
DocumentLinksButton.StartLink = this.props.View;
- } else {
+ } else if (!!!this.props.InMenu) {
DocumentLinksButton.EditLink = this.props.View;
DocumentLinksButton.EditLinkLoc = [e.clientX + 10, e.clientY];
}
}));
}
+
+ @action
+ onLinkClick = (e: React.MouseEvent): void => {
+ if (this.props.InMenu) {
+ DocumentLinksButton.StartLink = this.props.View;
+ //action(() => Doc.BrushDoc(this.props.View.Document));
+ } else if (!!!this.props.InMenu) {
+ DocumentLinksButton.EditLink = this.props.View;
+ DocumentLinksButton.EditLinkLoc = [e.clientX + 10, e.clientY];
+ }
+ }
+
+ @action
completeLink = (e: React.PointerEvent): void => {
setupMoveUpEvents(this, e, returnFalse, emptyFunction, action((e, doubleTap) => {
if (doubleTap) {
if (DocumentLinksButton.StartLink === this.props.View) {
DocumentLinksButton.StartLink = undefined;
+ // action((e: React.PointerEvent<HTMLDivElement>) => {
+ // Doc.UnBrushDoc(this.props.View.Document);
+ // });
} else {
DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View &&
DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
+
+ runInAction(() => {
+ LinkCreatedBox.popupX = e.screenX;
+ LinkCreatedBox.popupY = e.screenY - 120;
+ LinkCreatedBox.linkCreated = true;
+ setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
+ });
}
}
}));
}
+ @action
+ finishLinkClick = (e: React.MouseEvent) => {
+ if (DocumentLinksButton.StartLink === this.props.View) {
+ DocumentLinksButton.StartLink = undefined;
+ // action((e: React.PointerEvent<HTMLDivElement>) => {
+ // Doc.UnBrushDoc(this.props.View.Document);
+ // });
+ } else {
+ DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View &&
+ DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
+
+ runInAction(() => {
+ LinkCreatedBox.popupX = e.screenX;
+ LinkCreatedBox.popupY = e.screenY - 120;
+ LinkCreatedBox.linkCreated = true;
+ setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
+ });
+ }
+ }
+
@observable
public static EditLink: DocumentView | undefined;
public static EditLinkLoc: number[] = [0, 0];
@@ -83,22 +130,29 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
const links = DocListCast(this.props.View.props.Document.links);
return (!links.length || links[0].hidden) && !this.props.AlwaysOn ? (null) :
<div title="Drag(create link) Tap(view links)" ref={this._linkButton} style={{ minWidth: 20, minHeight: 20, position: "absolute", left: this.props.Offset?.[0] }}>
- <div className={"documentLinksButton"} style={{ backgroundColor: DocumentLinksButton.StartLink ? "transparent" : "" }}
- onPointerDown={this.onLinkButtonDown}
- onPointerLeave={action(() => LinkDocPreview.LinkInfo = undefined)}
- onPointerEnter={action(e => links.length && (LinkDocPreview.LinkInfo = {
- addDocTab: this.props.View.props.addDocTab,
- linkSrc: this.props.View.props.Document,
- linkDoc: links[0],
- Location: [e.clientX, e.clientY + 20]
- }))} >
- {links.length ? links.length : <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" />}
+ <div className={"documentLinksButton"} style={{
+ backgroundColor: DocumentLinksButton.StartLink ? "transparent" : "",
+ width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px", fontWeight: "bold"
+ }}
+ onPointerDown={this.onLinkButtonDown} onClick={this.onLinkClick}
+ // onPointerLeave={action(() => LinkDocPreview.LinkInfo = undefined)}
+ // onPointerEnter={action(e => links.length && (LinkDocPreview.LinkInfo = {
+ // addDocTab: this.props.View.props.addDocTab,
+ // linkSrc: this.props.View.props.Document,
+ // linkDoc: links[0],
+ // Location: [e.clientX, e.clientY + 20]
+ // }))}
+ >
+ {links.length && !!!this.props.InMenu ? links.length : <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" />}
</div>
- {DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View ? <div className={"documentLinksButton-endLink"} onPointerDown={this.completeLink} /> : (null)}
- {DocumentLinksButton.StartLink === this.props.View ? <div className={"documentLinksButton-startLink"} /> : (null)}
+ {DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View ? <div className={"documentLinksButton-endLink"}
+ style={{ width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px" }}
+ onPointerDown={this.completeLink} onClick={e => this.finishLinkClick(e)} /> : (null)}
+ {DocumentLinksButton.StartLink === this.props.View ? <div className={"documentLinksButton-startLink"}
+ style={{ width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px" }} /> : (null)}
</div>;
}
render() {
return this.linkButton;
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 21b6d8310..b38db9a1e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -29,7 +29,6 @@ import { SelectionManager } from "../../util/SelectionManager";
import SharingManager from '../../util/SharingManager';
import { Transform } from "../../util/Transform";
import { undoBatch, UndoManager } from "../../util/UndoManager";
-import { CollectionDockingView } from "../collections/CollectionDockingView";
import { CollectionView, CollectionViewType } from '../collections/CollectionView';
import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from '../ContextMenuItem';
@@ -42,6 +41,8 @@ import { LinkAnchorBox } from './LinkAnchorBox';
import { RadialMenu } from './RadialMenu';
import React = require("react");
import { DocumentLinksButton } from './DocumentLinksButton';
+import { MobileInterface } from '../../../mobile/MobileInterface';
+import { LinkCreatedBox } from './LinkCreatedBox';
library.add(fa.faEdit, fa.faTrash, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faCompressArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faAlignCenter, fa.faCaretSquareRight,
fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faLink, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, fa.faLock, fa.faLaptopCode, fa.faMale,
@@ -116,6 +117,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
private holdDisposer?: InteractionUtils.MultiTouchEventDisposer;
+ public get title() { return this.props.Document.title; }
public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
public get ContentDiv() { return this._mainCont.current; }
get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); }
@@ -181,10 +183,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1];
RadialMenu.Instance.openMenu(pt.pageX - 15, pt.pageY - 15);
- RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "map-pin", selected: -1 });
- RadialMenu.Instance.addItem({ description: "Delete this document", event: () => { this.props.ContainingCollectionView?.removeDocument(this.props.Document), RadialMenu.Instance.closeMenu(); }, icon: "layer-group", selected: -1 });
- RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, "onRight"), icon: "trash", selected: -1 });
- RadialMenu.Instance.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "folder", selected: -1 });
+ // RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "map-pin", selected: -1 });
+ RadialMenu.Instance.addItem({ description: "Delete", event: () => { this.props.ContainingCollectionView?.removeDocument(this.props.Document), RadialMenu.Instance.closeMenu(); }, icon: "external-link-square-alt", selected: -1 });
+ // RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, "onRight"), icon: "trash", selected: -1 });
+ RadialMenu.Instance.addItem({ description: "Pin", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin", selected: -1 });
+ RadialMenu.Instance.addItem({ description: "Open", event: () => MobileInterface.Instance.handleClick(this.props.Document), icon: "trash", selected: -1 });
SelectionManager.DeselectAll();
}
@@ -345,12 +348,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
// depending on the followLinkLocation property of the source (or the link itself as a fallback);
followLinkClick = async (altKey: boolean, ctrlKey: boolean, shiftKey: boolean) => {
const batch = UndoManager.StartBatch("follow link click");
- // open up target if it's not already in view ...
+ // open up target if it's not already in view ...
const createViewFunc = (doc: Doc, followLoc: string, finished: Opt<() => void>) => {
const targetFocusAfterDocFocus = () => {
const where = StrCast(this.Document.followLinkLocation) || followLoc;
const hackToCallFinishAfterFocus = () => {
- finished && setTimeout(finished, 0); // finished() needs to be called right after hackToCallFinishAfterFocus(), but there's no callback for that so we use the hacky timeout.
+ finished && setTimeout(finished, 0); // finished() needs to be called right after hackToCallFinishAfterFocus(), but there's no callback for that so we use the hacky timeout.
return false; // we must return false here so that the zoom to the document is not reversed. If it weren't for needing to call finished(), we wouldn't need this function at all since not having it is equivalent to returning false
};
this.props.addDocTab(doc, where) && this.props.focus(doc, BoolCast(this.Document.followLinkZoom, true), undefined, hackToCallFinishAfterFocus); // add the target and focus on it.
@@ -639,12 +642,27 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
e.stopPropagation();
de.complete.annoDragData.linkedToDoc = true;
+ runInAction(() => {
+ LinkCreatedBox.popupX = de.x;
+ LinkCreatedBox.popupY = de.y;
+ LinkCreatedBox.linkCreated = true;
+ setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
+ });
+
DocUtils.MakeLink({ doc: de.complete.annoDragData.annotationDocument }, { doc: this.props.Document }, "link");
}
if (de.complete.linkDragData) {
e.stopPropagation();
// const docs = await SearchUtil.Search(`data_l:"${destDoc[Id]}"`, true);
// const views = docs.map(d => DocumentManager.Instance.getDocumentView(d)).filter(d => d).map(d => d as DocumentView);
+
+ runInAction(() => {
+ LinkCreatedBox.popupX = de.x;
+ LinkCreatedBox.popupY = de.y;
+ LinkCreatedBox.linkCreated = true;
+ setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
+ });
+
de.complete.linkDragData.linkSourceDocument !== this.props.Document &&
(de.complete.linkDragData.linkDocument = DocUtils.MakeLink({ doc: de.complete.linkDragData.linkSourceDocument },
{ doc: this.props.Document }, `link`)); // TODODO this is where in text links get passed
@@ -724,17 +742,18 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
return;
}
e.persist();
- e?.stopPropagation();
+ e.stopPropagation();
- if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3 ||
- e.isDefaultPrevented()) {
- e.preventDefault();
+ if (Math.abs(this._downX - e?.clientX) > 3 || Math.abs(this._downY - e?.clientY) > 3 ||
+ e?.isDefaultPrevented()) {
+ e?.preventDefault();
return;
}
e.preventDefault();
}
const cm = ContextMenu.Instance;
+ if (!cm) return;
const customScripts = Cast(this.props.Document.contextMenuScripts, listSpec(ScriptField), []);
Cast(this.props.Document.contextMenuLabels, listSpec("string"), []).forEach((label, i) =>
@@ -875,7 +894,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
// DocumentViews should stop propagation of this event
e.stopPropagation();
}
- ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
+ cm.displayMenu(e.pageX - 15, e.pageY - 15);
!SelectionManager.IsSelected(this, true) && SelectionManager.SelectDoc(this, false);
});
const path = this.props.LibraryPath.reduce((p: string, d: Doc) => p + "/" + (Doc.AreProtosEqual(d, (Doc.UserDoc()["tabs-button-library"] as Doc).sourcePanel as Doc) ? "" : d.title), "");
@@ -943,7 +962,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
recommendations.documentIconHeight = 150;
recommendations.sourceDoc = this.props.Document;
recommendations.sourceDocContext = this.props.ContainingCollectionView!.props.Document;
- CollectionDockingView.AddRightSplit(recommendations, undefined);
+ this.props.addDocTab(recommendations, "onRight");
// RecommendationsBox.Instance.displayRecommendations(e.pageX + 100, e.pageY);
}
@@ -975,13 +994,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
body.href = urls[i];
bodies.push(body);
}
- CollectionDockingView.AddRightSplit(Docs.Create.SchemaDocument(headers, bodies, { title: `Showing External Recommendations for "${StrCast(doc.title)}"` }), undefined);
+ this.props.addDocTab(Docs.Create.SchemaDocument(headers, bodies, { title: `Showing External Recommendations for "${StrCast(doc.title)}"` }), "onRight");
this._showKPQuery = true;
this._queries = kps.toString();
}
// does Document set a layout prop
- // does Document set a layout prop
+ // does Document set a layout prop
setsLayoutProp = (prop: string) => this.props.Document[prop] !== this.props.Document["default" + prop[0].toUpperCase() + prop.slice(1)] && this.props.Document["default" + prop[0].toUpperCase() + prop.slice(1)];
// get the a layout prop by first choosing the prop from Document, then falling back to the layout doc otherwise.
getLayoutPropStr = (prop: string) => StrCast(this.setsLayoutProp(prop) ? this.props.Document[prop] : this.layoutDoc[prop]);
@@ -1169,8 +1188,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
render() {
- if (this.props.Document[AclSym] && this.props.Document[AclSym] === AclPrivate) return (null);
if (!(this.props.Document instanceof Doc)) return (null);
+ if (this.props.Document[AclSym] && this.props.Document[AclSym] === AclPrivate) return (null);
+ if (this.props.Document.hidden) return (null);
const backgroundColor = Doc.UserDoc().renderStyle === "comic" ? undefined : this.props.forcedBackgroundColor?.(this.Document) || StrCast(this.layoutDoc._backgroundColor) || StrCast(this.layoutDoc.backgroundColor) || StrCast(this.Document.backgroundColor) || this.props.backgroundColor?.(this.Document);
const opacity = Cast(this.layoutDoc._opacity, "number", Cast(this.layoutDoc.opacity, "number", Cast(this.Document.opacity, "number", null)));
const finalOpacity = this.props.opacity ? this.props.opacity() : opacity;
@@ -1189,7 +1209,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
id={this.props.Document[Id]}
ref={this._mainCont} onKeyDown={this.onKeyDown}
onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
- onPointerEnter={action(() => Doc.BrushDoc(this.props.Document))}
+ onPointerEnter={action(() => { Doc.BrushDoc(this.props.Document); })}
onPointerLeave={action((e: React.PointerEvent<HTMLDivElement>) => {
let entered = false;
const target = document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y);
@@ -1198,7 +1218,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
entered = true;
}
}
+ // if (this.props.Document !== DocumentLinksButton.StartLink?.Document) {
!entered && Doc.UnBrushDoc(this.props.Document);
+ //}
+
})}
style={{
transformOrigin: this._animateScalingTo ? "center center" : undefined,
@@ -1232,4 +1255,4 @@ Scripting.addGlobal(function toggleDetail(doc: any, layoutKey: string, otherKey:
const dv = DocumentManager.Instance.getDocumentView(doc);
if (dv?.props.Document.layoutKey === layoutKey) dv?.switchViews(otherKey !== "layout", otherKey.replace("layout_", ""));
else dv?.switchViews(true, layoutKey.replace("layout_", ""));
-}); \ No newline at end of file
+});
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index cf0b16c7c..5e8dd2497 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -5,7 +5,7 @@ import { createSchema, makeInterface } from '../../../fields/Schema';
import { DocComponent } from '../DocComponent';
import './FontIconBox.scss';
import { FieldView, FieldViewProps } from './FieldView';
-import { StrCast, Cast } from '../../../fields/Types';
+import { StrCast, Cast, NumCast } from '../../../fields/Types';
import { Utils } from "../../../Utils";
import { runInAction, observable, reaction, IReactionDisposer } from 'mobx';
import { Doc } from '../../../fields/Doc';
@@ -59,13 +59,14 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
render() {
const referenceDoc = (this.layoutDoc.dragFactory instanceof Doc ? this.layoutDoc.dragFactory : this.layoutDoc);
- const referenceLayout = Doc.Layout(referenceDoc);
+ const refLayout = Doc.Layout(referenceDoc);
return <button className="fontIconBox-outerDiv" title={StrCast(this.layoutDoc.title)} ref={this._ref} onContextMenu={this.specificContextMenu}
style={{
- background: StrCast(referenceLayout.backgroundColor),
+ padding: Cast(this.layoutDoc._xPadding, "number", null),
+ background: StrCast(refLayout._backgroundColor, StrCast(refLayout.backgroundColor)),
boxShadow: this.layoutDoc.ischecked ? `4px 4px 12px black` : undefined
}}>
- <FontAwesomeIcon className="fontIconBox-icon" icon={this.dataDoc.icon as any} color={this._foregroundColor} size="sm" />
+ <FontAwesomeIcon className="fontIconBox-icon" icon={this.dataDoc.icon as any} color={StrCast(this.layoutDoc.color, this._foregroundColor)} size="sm" />
{!this.rootDoc.label ? (null) : <div className="fontIconBox-label"> {StrCast(this.rootDoc.label).substring(0, 5)} </div>}
</button>;
}
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 15148d01d..c1b95b308 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -2,7 +2,7 @@
border-radius: inherit;
width: 100%;
height: 100%;
- position: absolute;
+ position: relative;
transform-origin: top left;
.imageBox-fader {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index c1c6f6baf..d16aa528c 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -120,7 +120,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
});
const files = await res.json();
const url = Utils.prepend(files[0].path);
- // upload to server with known URL
+ // upload to server with known URL
const audioDoc = Docs.Create.AudioDocument(url, { title: "audio test", _width: 200, _height: 32 });
audioDoc.treeViewExpandedView = "layout";
const audioAnnos = Cast(this.dataDoc[this.fieldKey + "-audioAnnotations"], listSpec(Doc));
@@ -174,14 +174,14 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
// }), icon: "expand-arrows-alt"
// });
- const existingAnalyze = ContextMenu.Instance.findByDescription("Analyzers...");
+ const existingAnalyze = ContextMenu.Instance?.findByDescription("Analyzers...");
const modes: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : [];
modes.push({ description: "Generate Tags", event: this.generateMetadata, icon: "tag" });
modes.push({ description: "Find Faces", event: this.extractFaces, icon: "camera" });
//modes.push({ description: "Recommend", event: this.extractText, icon: "brain" });
- !existingAnalyze && ContextMenu.Instance.addItem({ description: "Analyzers...", subitems: modes, icon: "hand-point-right" });
+ !existingAnalyze && ContextMenu.Instance?.addItem({ description: "Analyzers...", subitems: modes, icon: "hand-point-right" });
- ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
+ ContextMenu.Instance?.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
}
}
@@ -236,6 +236,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
if (this._curSuffix === "_m") this._mediumRetryCount++;
if (this._curSuffix === "_l") this._largeRetryCount++;
}
+
@action onError = (error: any) => {
const timeout = this._curSuffix === "_s" ? this._smallRetryCount : this._curSuffix === "_m" ? this._mediumRetryCount : this._largeRetryCount;
if (timeout < 5) {
@@ -490,4 +491,4 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
</CollectionFreeFormView>
</div >);
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/LinkAnchorBox.scss b/src/client/views/nodes/LinkAnchorBox.scss
index 710f2178b..42ef2958e 100644
--- a/src/client/views/nodes/LinkAnchorBox.scss
+++ b/src/client/views/nodes/LinkAnchorBox.scss
@@ -1,4 +1,5 @@
-.linkAnchorBox-cont, .linkAnchorBox-cont-small {
+.linkAnchorBox-cont,
+.linkAnchorBox-cont-small {
cursor: default;
position: absolute;
width: 15;
@@ -24,6 +25,6 @@
}
.linkAnchorBox-cont-small {
- width:5px;
- height:5px;
+ width: 5px;
+ height: 5px;
} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkCreatedBox.scss b/src/client/views/nodes/LinkCreatedBox.scss
new file mode 100644
index 000000000..3cbd38b55
--- /dev/null
+++ b/src/client/views/nodes/LinkCreatedBox.scss
@@ -0,0 +1,21 @@
+.linkCreatedBox-fade {
+ border: 1px solid rgb(100, 100, 100);
+
+
+ width: auto;
+ position: absolute;
+
+ height: auto;
+ z-index: 10000;
+ border-radius: 13px;
+ font-size: 13px;
+ white-space: nowrap;
+
+ color: rgb(100, 100, 100);
+ background-color: rgba(250, 250, 250, 0.85);
+ padding-top: 6.5px;
+ padding-bottom: 6.5px;
+ font-weight: bold;
+ padding-left: 9px;
+ padding-right: 9px;
+} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkCreatedBox.tsx b/src/client/views/nodes/LinkCreatedBox.tsx
new file mode 100644
index 000000000..d157d3fca
--- /dev/null
+++ b/src/client/views/nodes/LinkCreatedBox.tsx
@@ -0,0 +1,31 @@
+import React = require("react");
+import { observer } from "mobx-react";
+import { documentSchema } from "../../../fields/documentSchemas";
+import { makeInterface } from "../../../fields/Schema";
+import "./LinkCreatedBox.scss";
+import { observable, action } from "mobx";
+import { Fade } from "@material-ui/core";
+
+
+@observer
+export class LinkCreatedBox extends React.Component<{}> {
+
+ @observable public static linkCreated: boolean = false;
+ @observable public static popupX: number = 600;
+ @observable public static popupY: number = 250;
+
+ @action
+ public static changeLinkCreated = () => {
+ LinkCreatedBox.linkCreated = !LinkCreatedBox.linkCreated;
+ }
+
+ render() {
+ return <Fade in={LinkCreatedBox.linkCreated}>
+ <div className="linkCreatedBox-fade"
+ style={{
+ left: LinkCreatedBox.popupX ? LinkCreatedBox.popupX : 600,
+ top: LinkCreatedBox.popupY ? LinkCreatedBox.popupY : 250,
+ }}>Link Created</div>
+ </Fade>;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 92b443d3b..197dc8df4 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -10,6 +10,11 @@ import { Transform } from "../../util/Transform";
import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
import React = require("react");
import { DocumentView } from './DocumentView';
+import { sortAndDeduplicateDiagnostics } from 'typescript';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { LinkManager } from '../../util/LinkManager';
+import { DocumentLinksButton } from './DocumentLinksButton';
+import { ContextMenu } from '../ContextMenu';
interface Props {
linkDoc?: Doc;
@@ -24,6 +29,31 @@ export class LinkDocPreview extends React.Component<Props> {
@observable public static LinkInfo: Opt<{ linkDoc?: Doc; addDocTab: (document: Doc, where: string) => boolean, linkSrc: Doc; href?: string; Location: number[] }>;
@observable _targetDoc: Opt<Doc>;
@observable _toolTipText = "";
+ _editRef = React.createRef<HTMLDivElement>();
+
+ @action
+ deleteLink = (): void => {
+ this.props.linkDoc ? LinkManager.Instance.deleteLink(this.props.linkDoc) : null;
+ //this.props.showLinks();
+ LinkDocPreview.LinkInfo = undefined;
+ DocumentLinksButton.EditLink = undefined;
+ }
+
+ @action
+ onContextMenu = (e: React.MouseEvent) => {
+ DocumentLinksButton.EditLink = undefined;
+ LinkDocPreview.LinkInfo = undefined;
+ e.preventDefault();
+ ContextMenu.Instance.addItem({ description: "Follow Default Link", event: () => this.followDefault(), icon: "arrow-right" });
+ ContextMenu.Instance.displayMenu(e.clientX, e.clientY);
+ }
+
+ @action.bound
+ async followDefault() {
+ DocumentLinksButton.EditLink = undefined;
+ LinkDocPreview.LinkInfo = undefined;
+ this._targetDoc ? DocumentManager.Instance.FollowLink(this.props.linkDoc, this._targetDoc, doc => this.props.addDocTab(doc, "onRight"), false) : null;
+ }
componentDidUpdate() { this.updatePreview(); }
componentDidMount() { this.updatePreview(); }
@@ -56,15 +86,30 @@ export class LinkDocPreview extends React.Component<Props> {
this.props.addDocTab(Docs.Create.WebDocument(this.props.href, { title: this.props.href, _width: 200, _height: 400, UseCors: true }), "onRight");
}
}
- width = () => Math.min(350, NumCast(this._targetDoc?.[WidthSym](), 350));
- height = () => Math.min(350, NumCast(this._targetDoc?.[HeightSym](), 350));
+ width = () => Math.min(225, NumCast(this._targetDoc?.[WidthSym](), 225));
+ height = () => Math.min(225, NumCast(this._targetDoc?.[HeightSym](), 225));
@computed get targetDocView() {
return !this._targetDoc ?
- <div style={{ pointerEvents: "all", maxWidth: 350, maxHeight: 250, width: "100%", height: "100%", overflow: "hidden" }}>
+ <div style={{
+ pointerEvents: "all", maxWidth: 225, maxHeight: 225, width: "100%", height: "100%",
+ overflow: "hidden"
+ }}>
<div style={{ width: "100%", height: "100%", textOverflow: "ellipsis", }} onPointerDown={this.pointerDown}>
{this._toolTipText}
</div>
</div> :
+ // <div style={{
+ // border: "6px solid white",
+ // }}>
+ // <div style={{ backgroundColor: "white" }}> {this._targetDoc.title}
+ // <div className="wrapper" style={{ float: "right" }}>
+ // <div title="Delete link" className="button" style={{ display: "inline" }} ref={this._editRef} onPointerDown={this.deleteLink}>
+ // <FontAwesomeIcon className="fa-icon" icon="trash" size="sm" /></div>
+ // <div title="Follow link" className="button" style={{ display: "inline" }} onClick={this.followDefault} onContextMenu={this.onContextMenu}>
+ // <FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" />
+ // </div>
+ // </div>
+ // </div>
<ContentFittingDocumentView
Document={this._targetDoc}
LibraryPath={emptyPath}
@@ -92,6 +137,7 @@ export class LinkDocPreview extends React.Component<Props> {
NativeWidth={returnZero}
NativeHeight={returnZero}
/>;
+ //</div>;
}
render() {
@@ -99,9 +145,9 @@ export class LinkDocPreview extends React.Component<Props> {
style={{
position: "absolute", left: this.props.location[0],
top: this.props.location[1], width: this.width(), height: this.height(),
- boxShadow: "black 2px 2px 1em"
+ boxShadow: "black 2px 2px 1em", zIndex: 1000
}}>
{this.targetDocView}
</div>;
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index c6a83b662..f2ab37984 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -5,24 +5,26 @@
height: 100%;
width: 100%;
overflow: hidden;
- cursor:auto;
+ cursor: auto;
transform-origin: top left;
z-index: 0;
+
.pdfBox-ui {
position: absolute;
- width: 100%;
- height: 100%;
- z-index: 1;
- pointer-events: none;
+ width: 100%;
+ height: 100%;
+ z-index: 1;
+ pointer-events: none;
- .pdfBox-pageNums {
+ .pdfBox-pageNums {
display: flex;
flex-direction: row;
height: 25px;
position: absolute;
left: 5px;
top: 5px;
- .pdfBox-overlayButton-fwd,
+
+ .pdfBox-overlayButton-fwd,
.pdfBox-overlayButton-back {
background: #121721;
height: 25px;
@@ -34,29 +36,29 @@
border-radius: 3px;
pointer-events: all;
}
- }
-
- .pdfBox-overlayButton {
- border-bottom-left-radius: 50%;
- display: flex;
- justify-content: space-evenly;
- align-items: center;
- height: 20px;
- background: none;
- padding: 0;
- position: absolute;
- pointer-events: all;
-
- .pdfBox-overlayButton-arrow {
- width: 0;
- height: 0;
- border-top: 10px solid transparent;
- border-bottom: 10px solid transparent;
- border-right: 15px solid #121721;
- transition: all 0.5s;
- }
-
- .pdfBox-overlayButton-iconCont {
+ }
+
+ .pdfBox-overlayButton {
+ border-bottom-left-radius: 50%;
+ display: flex;
+ justify-content: space-evenly;
+ align-items: center;
+ height: 20px;
+ background: none;
+ padding: 0;
+ position: absolute;
+ pointer-events: all;
+
+ .pdfBox-overlayButton-arrow {
+ width: 0;
+ height: 0;
+ border-top: 10px solid transparent;
+ border-bottom: 10px solid transparent;
+ border-right: 15px solid #121721;
+ transition: all 0.5s;
+ }
+
+ .pdfBox-overlayButton-iconCont {
background: #121721;
height: 20px;
width: 25px;
@@ -66,11 +68,11 @@
justify-content: center;
border-radius: 3px;
pointer-events: all;
- }
+ }
}
- .pdfBox-nextIcon,
- .pdfBox-prevIcon {
+ .pdfBox-nextIcon,
+ .pdfBox-prevIcon {
background: #121721;
height: 20px;
width: 25px;
@@ -81,96 +83,97 @@
border-radius: 3px;
pointer-events: all;
padding: 0px;
- }
-
- .pdfBox-overlayButton:hover {
- background: none;
- }
-
-
- .pdfBox-settingsCont {
- position: absolute;
- right: 0;
- top: 3;
- pointer-events: all;
-
- .pdfBox-settingsButton {
- border-bottom-left-radius: 50%;
- display: flex;
- justify-content: space-evenly;
- align-items: center;
- height: 20px;
- background: none;
- padding: 0;
-
- .pdfBox-settingsButton-arrow {
- width: 0;
- height: 0;
- border-top: 10px solid transparent;
- border-bottom: 10px solid transparent;
- border-right: 15px solid #121721;
- transition: all 0.5s;
- }
-
- .pdfBox-settingsButton-iconCont {
- background: #121721;
- height: 20px;
- width: 25px;
- display: flex;
- justify-content: center;
- align-items: center;
- margin-left: -2px;
- border-radius: 3px;
- }
- }
-
- .pdfBox-settingsButton:hover {
- background: none;
- }
-
- .pdfBox-settingsFlyout {
- position: absolute;
- background: #323232;
- box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
- right: 20px;
- border-radius: 7px;
- padding: 20px;
- display: flex;
- flex-direction: column;
- font-size: 14px;
- transition: all 0.5s;
-
- .pdfBox-settingsFlyout-title {
- color: white;
- }
-
- .pdfBox-settingsFlyout-kvpInput {
- margin-top: 20px;
- display: grid;
- grid-template-columns: 47.5% 5% 47.5%;
- }
- }
- }
-
- .pdfBox-overlayCont {
- position: absolute;
- width: calc(100% - 40px);
- height: 20px;
- background: #121721;
- bottom: 0;
- display: flex;
- justify-content: center;
- align-items: center;
- overflow: hidden;
- transition: left .5s;
- pointer-events: all;
-
- .pdfBox-searchBar {
- width: 70%;
- font-size: 14px;
- }
- }
+ }
+
+ .pdfBox-overlayButton:hover {
+ background: none;
+ }
+
+
+ .pdfBox-settingsCont {
+ position: absolute;
+ right: 0;
+ top: 3;
+ pointer-events: all;
+
+ .pdfBox-settingsButton {
+ border-bottom-left-radius: 50%;
+ display: flex;
+ justify-content: space-evenly;
+ align-items: center;
+ height: 20px;
+ background: none;
+ padding: 0;
+
+ .pdfBox-settingsButton-arrow {
+ width: 0;
+ height: 0;
+ border-top: 10px solid transparent;
+ border-bottom: 10px solid transparent;
+ border-right: 15px solid #121721;
+ transition: all 0.5s;
+ }
+
+ .pdfBox-settingsButton-iconCont {
+ background: #121721;
+ height: 20px;
+ width: 25px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-left: -2px;
+ border-radius: 3px;
+ }
+ }
+
+ .pdfBox-settingsButton:hover {
+ background: none;
+ }
+
+ .pdfBox-settingsFlyout {
+ position: absolute;
+ background: #323232;
+ box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
+ right: 20px;
+ border-radius: 7px;
+ padding: 20px;
+ display: flex;
+ flex-direction: column;
+ font-size: 14px;
+ transition: all 0.5s;
+
+ .pdfBox-settingsFlyout-title {
+ color: white;
+ }
+
+ .pdfBox-settingsFlyout-kvpInput {
+ margin-top: 20px;
+ display: grid;
+ grid-template-columns: 47.5% 5% 47.5%;
+ }
+ }
+ }
+
+ .pdfBox-overlayCont {
+ position: absolute;
+ width: calc(100% - 40px);
+ height: 20px;
+ background: #121721;
+ bottom: 0;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ overflow: hidden;
+ transition: left .5s;
+ pointer-events: all;
+
+ .pdfBox-searchBar {
+ width: 70%;
+ font-size: 14px;
+ }
+ }
}
+
.pdfBox-title-outer {
width: 150%;
height: 100%;
@@ -179,9 +182,9 @@
z-index: 0;
background: lightslategray;
transform-origin: top left;
-
+
.pdfBox-title {
- color:lightgray;
+ color: lightgray;
margin-top: auto;
margin-bottom: auto;
transform-origin: 42% 15%;
@@ -217,4 +220,76 @@
}
}
}
-} \ No newline at end of file
+}
+
+// CSS adjusted for mobile devices
+@media only screen and (max-device-width: 480px) {
+
+ .pdfBox .pdfBox-ui .pdfBox-settingsCont .pdfBox-settingsButton,
+ .pdfBox-interactive .pdfBox-ui .pdfBox-settingsCont .pdfBox-settingsButton {
+ height: 60px;
+
+ .pdfBox-settingsButton-iconCont {
+ height: 60px;
+ width: 75px;
+ font-size: 30px;
+ }
+
+ .pdfBox-settingsButton-arrow {
+ height: 60;
+ border-top: 30px solid transparent;
+ border-bottom: 30px solid transparent;
+ border-right: 30px solid #121721;
+ }
+ }
+
+
+
+ .pdfBox .pdfBox-ui .pdfBox-settingsCont .pdfBox-settingsFlyout,
+ .pdfBox-interactive .pdfBox-ui .pdfBox-settingsCont .pdfBox-settingsFlyout {
+ font-size: 30px;
+ }
+
+
+
+ .pdfBox .pdfBox-ui .pdfBox-overlayCont,
+ .pdfBox-interactive .pdfBox-ui .pdfBox-overlayCont {
+ height: 60px;
+
+ .pdfBox-searchBar {
+ font-size: 40px;
+ }
+ }
+
+ .pdfBox .pdfBox-ui .pdfBox-overlayButton,
+ .pdfBox-interactive .pdfBox-ui .pdfBox-overlayButton {
+ height: 60px;
+
+ .pdfBox-overlayButton-iconCont {
+ height: 60px;
+ width: 75px;
+ font-size: 30;
+ }
+
+ .pdfBox-overlayButton-arrow {
+ border-top: 30px solid transparent;
+ border-bottom: 30px solid transparent;
+ border-right: 30px solid #121721;
+ }
+ }
+
+ button.pdfBox-search {
+ font-size: 30px;
+ width: 50px;
+ height: 50px;
+ }
+
+ .pdfBox .pdfBox-ui .pdfBox-nextIcon,
+ .pdfBox .pdfBox-ui .pdfBox-prevIcon,
+ .pdfBox-interactive .pdfBox-ui .pdfBox-nextIcon,
+ .pdfBox-interactive .pdfBox-ui .pdfBox-prevIcon {
+ height: 50px;
+ width: 50px;
+ font-size: 30px;
+ }
+}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index eb2a85eeb..1c5825a8f 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -157,7 +157,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
<div className="pdfBox-overlayCont" key="cont" onPointerDown={(e) => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}>
<button className="pdfBox-overlayButton" title={searchTitle} />
<input className="pdfBox-searchBar" placeholder="Search" ref={this._searchRef} onChange={this.searchStringChanged} onKeyDown={e => e.keyCode === KeyCodes.ENTER && this.search(this._searchString, !e.shiftKey)} />
- <button title="Search" onClick={e => this.search(this._searchString, !e.shiftKey)}>
+ <button className="pdfBox-search" title="Search" onClick={e => this.search(this._searchString, !e.shiftKey)}>
<FontAwesomeIcon icon="search" size="sm" color="white" /></button>
<button className="pdfBox-prevIcon " title="Previous Annotation" onClick={this.prevAnnotation} >
<FontAwesomeIcon style={{ color: "white" }} icon={"arrow-up"} size="lg" />
@@ -189,7 +189,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
<FontAwesomeIcon style={{ color: "white" }} icon="cog" size="lg" />
</div>
</button>
- <div className="pdfBox-settingsFlyout" style={{ right: `${this._flyout ? 20 : -600}px` }} >
+ <div className="pdfBox-settingsFlyout" style={{ right: `${this._flyout ? 20 : -1000}px` }} >
<div className="pdfBox-settingsFlyout-title">
Annotation View Settings
</div>
@@ -229,7 +229,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
const classname = "pdfBox" + (this.active() ? "-interactive" : "");
return <div className={classname} style={{
width: !this.props.Document._fitWidth ? this.Document._nativeWidth || 0 : `${100 / this.contentScaling}%`,
- height: !this.props.Document._fitWidth ? this.Document._nativeHeight || 0 : `${100 / this.contentScaling}%`,
+ //height adjusted for mobile (window.screen.width > 600)
+ height: !this.props.Document._fitWidth && (window.screen.width > 600) ? this.Document._nativeHeight || 0 : `${100 / this.contentScaling}%`,
transform: `scale(${this.contentScaling})`
}} >
<div className="pdfBox-title-outer">
@@ -241,7 +242,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
isChildActive = (outsideReaction?: boolean) => this._isChildActive;
@computed get renderPdfView() {
const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField);
- return <div className={"pdfBox"} onContextMenu={this.specificContextMenu} style={{ height: this.props.Document._scrollTop && !this.Document._fitWidth ? NumCast(this.Document._height) * this.props.PanelWidth() / NumCast(this.Document._width) : undefined }}>
+ return <div className={"pdfBox"} onContextMenu={this.specificContextMenu} style={{ height: this.props.Document._scrollTop && !this.Document._fitWidth && (window.screen.width > 600) ? NumCast(this.Document._height) * this.props.PanelWidth() / NumCast(this.Document._width) : undefined }}>
<PDFViewer {...this.props} pdf={this._pdf!} url={pdfUrl!.url.pathname} active={this.props.active} loaded={this.loaded}
setPdfViewer={this.setPdfViewer} ContainingCollectionView={this.props.ContainingCollectionView}
renderDepth={this.props.renderDepth} PanelHeight={this.props.PanelHeight} PanelWidth={this.props.PanelWidth}
diff --git a/src/client/views/nodes/PresBox.scss b/src/client/views/nodes/PresBox.scss
index 9b040e6fe..9c2daf5d3 100644
--- a/src/client/views/nodes/PresBox.scss
+++ b/src/client/views/nodes/PresBox.scss
@@ -150,4 +150,51 @@
.presBox-forward {
right: 5;
}
-} \ No newline at end of file
+}
+
+// CSS adjusted for mobile devices
+@media only screen and (max-device-width: 480px) {
+ .presBox-cont .presBox-buttons {
+ position: absolute;
+ top: 70%;
+ left: 50%;
+ transform: translate(-50%, 0);
+ width: max-content;
+ height: 15%;
+ z-index: 2;
+ align-items: center;
+ background: rgba(0, 0, 0, 0);
+ display: inline-flex;
+
+ .presBox-button {
+ margin-top: 5%;
+ height: 250;
+ width: 300;
+ font-size: 100;
+ display: flex;
+ align-items: center;
+ background: #323232;
+ color: white;
+ }
+
+ .presBox-viewPicker {
+ top: -70;
+ left: 2.5%;
+ height: 50;
+ width: 95%;
+ font-size: 30px;
+ position: absolute;
+ min-width: 50px;
+ }
+ }
+
+ .presBox-cont .presBox-listCont {
+ top: 50;
+ height: calc(100% - 80px);
+ }
+
+ .input,
+ .select {
+ font-size: 100%;
+ }
+}
diff --git a/src/client/views/nodes/RadialMenu.tsx b/src/client/views/nodes/RadialMenu.tsx
index ddfdb67b4..a3ac09a11 100644
--- a/src/client/views/nodes/RadialMenu.tsx
+++ b/src/client/views/nodes/RadialMenu.tsx
@@ -1,7 +1,6 @@
import React = require("react");
import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
-import MobileInterface from "../../../mobile/MobileInterface";
import "./RadialMenu.scss";
import { RadialMenuItem, RadialMenuProps } from "./RadialMenuItem";
@@ -26,7 +25,6 @@ export class RadialMenu extends React.Component {
catchTouch = (te: React.TouchEvent) => {
- console.log("caught");
te.stopPropagation();
te.preventDefault();
}
@@ -38,7 +36,6 @@ export class RadialMenu extends React.Component {
this._mouseY = e.clientY;
this.used = false;
document.addEventListener("pointermove", this.onPointerMove);
-
}
@observable
@@ -176,7 +173,6 @@ export class RadialMenu extends React.Component {
@action
openMenu = (x: number, y: number) => {
-
this._pageX = x;
this._pageY = y;
this._shouldDisplay;
@@ -216,7 +212,7 @@ export class RadialMenu extends React.Component {
render() {
- if (!this._display || MobileInterface.Instance) {
+ if (!this._display) {
return null;
}
const style = this._yRelativeToTop ? { left: this._pageX - 130, top: this._pageY - 130 } :
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 678494b27..afdd8fea2 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -37,6 +37,7 @@
position: absolute;
}
}
+
.formattedTextBox-outer {
position: relative;
overflow: auto;
@@ -72,7 +73,7 @@
.collectionfreeformview-container {
position: relative;
}
-
+
>.formattedTextBox-sidebar-handle {
right: unset;
left: -5;
@@ -95,7 +96,7 @@
.formattedTextBox-inner-rounded, .formattedTextBox-inner-rounded-selected,
.formattedTextBox-inner, .formattedTextBox-inner-selected {
height: 100%;
- white-space: pre-wrap;
+ white-space: pre-wrap;
.ProseMirror:hover {
background: rgba(200,200,200,0.8);
}
@@ -262,19 +263,19 @@ footnote::after {
border:unset;
padding:0px;
}
-
+
.prosemirror-links a {
float: left;
color: white;
text-decoration: none;
border-radius: 3px;
}
-
+
.prosemirror-links a:hover {
background-color: #eee;
color: black;
}
-
+
.prosemirror-anchor:hover .prosemirror-links {
display: grid;
}
@@ -302,27 +303,26 @@ footnote::after {
font-family: inherit;
}
ol {
- margin-left: 1em;
font-family: inherit;
}
- .bullet { p {display: inline-block; font-family: inherit} margin-left: 0; }
- .bullet1 { p {display: inline-block; font-family: inherit} }
- .bullet2,.bullet3,.bullet4,.bullet5,.bullet6 { p {display: inline-block; font-family: inherit} font-size: smaller; }
+ .bullet { p { font-family: inherit} margin-left: 0; }
+ .bullet1 { p { font-family: inherit} }
+ .bullet2,.bullet3,.bullet4,.bullet5,.bullet6 { p { font-family: inherit} font-size: smaller; }
.decimal1-ol { counter-reset: deci1; p {display: inline-block; font-family: inherit} margin-left: 0; }
- .decimal2-ol { counter-reset: deci2; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 1em;}
- .decimal3-ol { counter-reset: deci3; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 2em;}
- .decimal4-ol { counter-reset: deci4; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 3em;}
+ .decimal2-ol { counter-reset: deci2; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 2.1em;}
+ .decimal3-ol { counter-reset: deci3; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 2.85em;}
+ .decimal4-ol { counter-reset: deci4; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 3.85em;}
.decimal5-ol { counter-reset: deci5; p {display: inline-block; font-family: inherit} font-size: smaller; }
.decimal6-ol { counter-reset: deci6; p {display: inline-block; font-family: inherit} font-size: smaller; }
.decimal7-ol { counter-reset: deci7; p {display: inline-block; font-family: inherit} font-size: smaller; }
.multi1-ol { counter-reset: multi1; p {display: inline-block; font-family: inherit} margin-left: 0; padding-left: 1.2em }
- .multi2-ol { counter-reset: multi2; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 1.4em;}
- .multi3-ol { counter-reset: multi3; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 2em;}
- .multi4-ol { counter-reset: multi4; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 3.4em;}
-
- .bullet:before, .bullet1:before, .bullet2:before, .bullet3:before, .bullet4:before, .bullet5:before { transition: 0.5s; display: inline-block; vertical-align: top; margin-left: -1em; width: 1em; content:" " }
+ .multi2-ol { counter-reset: multi2; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 2em;}
+ .multi3-ol { counter-reset: multi3; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 2.85em;}
+ .multi4-ol { counter-reset: multi4; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 3.85em;}
+
+ //.bullet:before, .bullet1:before, .bullet2:before, .bullet3:before, .bullet4:before, .bullet5:before { transition: 0.5s; display: inline-block; vertical-align: top; margin-left: -1em; width: 1em; content:" " }
.decimal1:before { transition: 0.5s;counter-increment: deci1; display: inline-block; vertical-align: top; margin-left: -1em; width: 1em; content: counter(deci1) ". "; }
.decimal2:before { transition: 0.5s;counter-increment: deci2; display: inline-block; vertical-align: top; margin-left: -2.1em; width: 2.1em; content: counter(deci1) "."counter(deci2) ". "; }
@@ -331,8 +331,8 @@ footnote::after {
.decimal5:before { transition: 0.5s;counter-increment: deci5; display: inline-block; vertical-align: top; margin-left: -2em; width: 5em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) ". "; }
.decimal6:before { transition: 0.5s;counter-increment: deci6; display: inline-block; vertical-align: top; margin-left: -2em; width: 6em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) ". "; }
.decimal7:before { transition: 0.5s;counter-increment: deci7; display: inline-block; vertical-align: top; margin-left: -2em; width: 7em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) "."counter(deci7) ". "; }
-
- .multi1:before { transition: 0.5s;counter-increment: multi1; display: inline-block; vertical-align: top; margin-left: -1em; width: 1.2em; content: counter(multi1, upper-alpha) ". "; }
+
+ .multi1:before { transition: 0.5s;counter-increment: multi1; display: inline-block; vertical-align: top; margin-left: -1.3em; width: 1.2em; content: counter(multi1, upper-alpha) ". "; }
.multi2:before { transition: 0.5s;counter-increment: multi2; display: inline-block; vertical-align: top; margin-left: -2em; width: 2em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) ". "; }
.multi3:before { transition: 0.5s;counter-increment: multi3; display: inline-block; vertical-align: top; margin-left: -2.85em; width:2.85em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) ". "; }
.multi4:before { transition: 0.5s;counter-increment: multi4; display: inline-block; vertical-align: top; margin-left: -4.2em; width: 4.2em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) "."counter(multi4, lower-roman) ". "; }
@@ -346,4 +346,284 @@ footnote::after {
.ProseMirror:hover {
background: unset;
}
-} \ No newline at end of file
+}
+
+@media only screen and (max-width: 1000px) {
+ @import "../../globalCssVariables";
+
+ .ProseMirror {
+ width: 100%;
+ height: 100%;
+ min-height: 100%;
+ }
+
+ .ProseMirror:focus {
+ outline: none !important;
+ }
+
+ .formattedTextBox-cont {
+ touch-action: none;
+ cursor: text;
+ background: inherit;
+ padding: 0;
+ border-width: 0px;
+ border-radius: inherit;
+ border-color: $intermediate-color;
+ box-sizing: border-box;
+ background-color: inherit;
+ border-style: solid;
+ overflow-y: auto;
+ overflow-x: hidden;
+ color: initial;
+ max-height: 100%;
+ display: flex;
+ flex-direction: row;
+ transition: opacity 1s;
+
+ .formattedTextBox-dictation {
+ height: 12px;
+ width: 10px;
+ top: 0px;
+ left: 0px;
+ position: absolute;
+ }
+ }
+
+ .formattedTextBox-outer {
+ position: relative;
+ overflow: auto;
+ display: inline-block;
+ width: 100%;
+ height: 100%;
+ }
+
+ .formattedTextBox-sidebar-handle {
+ position: absolute;
+ top: calc(50% - 17.5px);
+ width: 10px;
+ height: 35px;
+ background: lightgray;
+ border-radius: 20px;
+ cursor:grabbing;
+ }
+
+ .formattedTextBox-cont>.formattedTextBox-sidebar-handle {
+ right: 0;
+ left: unset;
+ }
+
+ .formattedTextBox-sidebar,
+ .formattedTextBox-sidebar-inking {
+ border-left: dashed 1px black;
+ height: 100%;
+ display: inline-block;
+ position: absolute;
+ right: 0;
+
+ .collectionfreeformview-container {
+ position: relative;
+ }
+
+ >.formattedTextBox-sidebar-handle {
+ right: unset;
+ left: -5;
+ }
+ }
+
+ .formattedTextBox-sidebar-inking {
+ pointer-events: all;
+ }
+
+ .formattedTextBox-inner-rounded {
+ height: 70%;
+ width: 85%;
+ position: absolute;
+ overflow: auto;
+ top: 15%;
+ left: 10%;
+ }
+
+ .formattedTextBox-inner-rounded,
+ .formattedTextBox-inner {
+ height: 100%;
+ white-space: pre-wrap;
+ hr {
+ display: block;
+ unicode-bidi: isolate;
+ margin-block-start: 0.5em;
+ margin-block-end: 0.5em;
+ margin-inline-start: auto;
+ margin-inline-end: auto;
+ overflow: hidden;
+ border-style: inset;
+ border-width: 1px;
+ }
+ }
+
+ // .menuicon {
+ // display: inline-block;
+ // border-right: 1px solid rgba(0, 0, 0, 0.2);
+ // color: #888;
+ // line-height: 1;
+ // padding: 0 7px;
+ // margin: 1px;
+ // cursor: pointer;
+ // text-align: center;
+ // min-width: 1.4em;
+ // }
+
+ .strong,
+ .heading {
+ font-weight: bold;
+ }
+
+ .em {
+ font-style: italic;
+ }
+
+ .userMarkOpen {
+ background: rgba(255, 255, 0, 0.267);
+ display: inline;
+ }
+
+ .userMark {
+ background: rgba(255, 255, 0, 0.267);
+ font-size: 2px;
+ display: inline-grid;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ width: 10px;
+ min-height: 10px;
+ text-align: center;
+ align-content: center;
+ }
+
+ footnote {
+ display: inline-block;
+ position: relative;
+ cursor: pointer;
+
+ div {
+ padding: 0 !important;
+ }
+ }
+
+ footnote::after {
+ content: counter(prosemirror-footnote);
+ vertical-align: super;
+ font-size: 75%;
+ counter-increment: prosemirror-footnote;
+ }
+
+ .ProseMirror {
+ counter-reset: prosemirror-footnote;
+ }
+
+ .footnote-tooltip {
+ cursor: auto;
+ font-size: 75%;
+ position: absolute;
+ left: -30px;
+ top: calc(100% + 10px);
+ background: silver;
+ padding: 3px;
+ border-radius: 2px;
+ max-width: 100px;
+ min-width: 50px;
+ width: max-content;
+ }
+
+ .prosemirror-attribution {
+ font-size: 8px;
+ }
+
+ .footnote-tooltip::before {
+ border: 5px solid silver;
+ border-top-width: 0px;
+ border-left-color: transparent;
+ border-right-color: transparent;
+ position: absolute;
+ top: -5px;
+ left: 27px;
+ content: " ";
+ height: 0;
+ width: 0;
+ }
+
+
+ .formattedTextBox-inlineComment {
+ position: relative;
+ width: 40px;
+ height: 20px;
+ &::before {
+ content: "→";
+ }
+ &:hover {
+ background: orange;
+ }
+ }
+
+ .formattedTextBox-summarizer {
+ opacity: 0.5;
+ position: relative;
+ width: 40px;
+ height: 20px;
+ &::after {
+ content: "←";
+ }
+ }
+
+ .formattedTextBox-summarizer-collapsed {
+ opacity: 0.5;
+ position: relative;
+ width: 40px;
+ height: 20px;
+ &::after {
+ content: "...";
+ }
+ }
+
+ .ProseMirror {
+ touch-action: none;
+ span {
+ font-family: inherit;
+ }
+
+ ol, ul {
+ counter-reset: deci1 0 multi1 0;
+ padding-left: 1em;
+ font-family: inherit;
+ }
+ ol {
+ margin-left: 1em;
+ font-family: inherit;
+ }
+
+ .decimal1-ol { counter-reset: deci1; p {display: inline; font-family: inherit} margin-left: 0; }
+ .decimal2-ol { counter-reset: deci2; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 1em;}
+ .decimal3-ol { counter-reset: deci3; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 2em;}
+ .decimal4-ol { counter-reset: deci4; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 3em;}
+ .decimal5-ol { counter-reset: deci5; p {display: inline; font-family: inherit} font-size: smaller; }
+ .decimal6-ol { counter-reset: deci6; p {display: inline; font-family: inherit} font-size: smaller; }
+ .decimal7-ol { counter-reset: deci7; p {display: inline; font-family: inherit} font-size: smaller; }
+
+ .multi1-ol { counter-reset: multi1; p {display: inline; font-family: inherit} margin-left: 0; padding-left: 1.2em }
+ .multi2-ol { counter-reset: multi2; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 1.4em;}
+ .multi3-ol { counter-reset: multi3; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 2em;}
+ .multi4-ol { counter-reset: multi4; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 3.4em;}
+
+ .decimal1:before { transition: 0.5s;counter-increment: deci1; display: inline-block; margin-left: -1em; width: 1em; content: counter(deci1) ". "; }
+ .decimal2:before { transition: 0.5s;counter-increment: deci2; display: inline-block; margin-left: -2.1em; width: 2.1em; content: counter(deci1) "."counter(deci2) ". "; }
+ .decimal3:before { transition: 0.5s;counter-increment: deci3; display: inline-block; margin-left: -2.85em;width: 2.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) ". "; }
+ .decimal4:before { transition: 0.5s;counter-increment: deci4; display: inline-block; margin-left: -3.85em;width: 3.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) ". "; }
+ .decimal5:before { transition: 0.5s;counter-increment: deci5; display: inline-block; margin-left: -2em; width: 5em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) ". "; }
+ .decimal6:before { transition: 0.5s;counter-increment: deci6; display: inline-block; margin-left: -2em; width: 6em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) ". "; }
+ .decimal7:before { transition: 0.5s;counter-increment: deci7; display: inline-block; margin-left: -2em; width: 7em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) "."counter(deci7) ". "; }
+
+ .multi1:before { transition: 0.5s;counter-increment: multi1; display: inline-block; margin-left: -1em; width: 1.2em; content: counter(multi1, upper-alpha) ". "; }
+ .multi2:before { transition: 0.5s;counter-increment: multi2; display: inline-block; margin-left: -2em; width: 2em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) ". "; }
+ .multi3:before { transition: 0.5s;counter-increment: multi3; display: inline-block; margin-left: -2.85em; width:2.85em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) ". "; }
+ .multi4:before { transition: 0.5s;counter-increment: multi4; display: inline-block; margin-left: -4.2em; width: 4.2em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) "."counter(multi4, lower-roman) ". "; }
+ }
+}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 2cb55e0fa..8e3087672 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -8,7 +8,7 @@ import { baseKeymap, selectAll } from "prosemirror-commands";
import { history } from "prosemirror-history";
import { inputRules } from 'prosemirror-inputrules';
import { keymap } from "prosemirror-keymap";
-import { Fragment, Mark, Node, Slice } from "prosemirror-model";
+import { Fragment, Mark, Node, Slice, Schema } from "prosemirror-model";
import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "prosemirror-state";
import { ReplaceStep } from 'prosemirror-transform';
import { EditorView } from "prosemirror-view";
@@ -16,6 +16,7 @@ import { DateField } from '../../../../fields/DateField';
import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclSym } from "../../../../fields/Doc";
import { documentSchema } from '../../../../fields/documentSchemas';
import applyDevTools = require("prosemirror-dev-tools");
+import { removeMarkWithAttrs } from "./prosemirrorPatches";
import { Id } from '../../../../fields/FieldSymbols';
import { InkTool } from '../../../../fields/InkField';
import { PrefetchProxy } from '../../../../fields/Proxy';
@@ -32,7 +33,7 @@ import { DocumentType } from '../../../documents/DocumentTypes';
import { DictationManager } from '../../../util/DictationManager';
import { DragManager } from "../../../util/DragManager";
import { makeTemplate } from '../../../util/DropConverter';
-import buildKeymap from "./ProsemirrorExampleTransfer";
+import buildKeymap, { updateBullets } from "./ProsemirrorExampleTransfer";
import RichTextMenu from './RichTextMenu';
import { RichTextRules } from "./RichTextRules";
@@ -56,8 +57,9 @@ import { DocumentButtonBar } from '../../DocumentButtonBar';
import { AudioBox } from '../AudioBox';
import { FieldView, FieldViewProps } from "../FieldView";
import "./FormattedTextBox.scss";
-import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './FormattedTextBoxComment';
+import { FormattedTextBoxComment, formattedTextBoxCommentPlugin, findLinkMark } from './FormattedTextBoxComment';
import React = require("react");
+import { DocumentManager } from '../../../util/DocumentManager';
library.add(faEdit);
library.add(faSmile, faTextHeight, faUpload);
@@ -68,15 +70,10 @@ export interface FormattedTextBoxProps {
xMargin?: number; // used to override document's settings for xMargin --- see CollectionCarouselView
yMargin?: number;
}
-
-const richTextSchema = createSchema({
- documentText: "string",
-});
-
export const GoogleRef = "googleDocId";
-type RichTextDocument = makeInterface<[typeof richTextSchema, typeof documentSchema]>;
-const RichTextDocument = makeInterface(richTextSchema, documentSchema);
+type RichTextDocument = makeInterface<[typeof documentSchema]>;
+const RichTextDocument = makeInterface(documentSchema);
type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
@@ -86,14 +83,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
public static blankState = () => EditorState.create(FormattedTextBox.Instance.config);
public static Instance: FormattedTextBox;
public ProseRef?: HTMLDivElement;
+ public get EditorView() { return this._editorView; }
private _ref: React.RefObject<HTMLDivElement> = React.createRef();
private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef();
private _editorView: Opt<EditorView>;
private _applyingChange: boolean = false;
private _searchIndex = 0;
+ private _cachedLinks: Doc[] = [];
private _undoTyping?: UndoManager.Batch;
private _disposers: { [name: string]: IReactionDisposer } = {};
- private dropDisposer?: DragManager.DragDropDisposer;
+ private _dropDisposer?: DragManager.DragDropDisposer;
@computed get _recording() { return this.dataDoc.audioState === "recording"; }
set _recording(value) { this.dataDoc.audioState = value ? "recording" : undefined; }
@@ -145,6 +144,33 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
public get CurrentDiv(): HTMLDivElement { return this._ref.current!; }
+ // removes all hyperlink anchors for the removed linkDoc
+ // TODO: bcz: Argh... if a section of text has multiple anchors, this should just remove the intended one.
+ // but since removing one anchor from the list of attr anchors isn't implemented, this will end up removing nothing.
+ public RemoveLinkFromDoc(linkDoc?: Doc) {
+ const state = this._editorView?.state;
+ if (state && linkDoc && this._editorView) {
+ var allLinks: any[] = [];
+ state.doc.nodesBetween(0, state.doc.nodeSize - 2, (node: any, pos: number, parent: any) => {
+ const foundMark = findLinkMark(node.marks);
+ const newHrefs = foundMark?.attrs.allLinks.filter((a: any) => a.href.includes(linkDoc[Id])) || [];
+ allLinks = newHrefs.length ? newHrefs : allLinks;
+ return true;
+ });
+ if (allLinks.length) {
+ this._editorView.dispatch(removeMarkWithAttrs(state.tr, 0, state.doc.nodeSize - 2, state.schema.marks.linkAnchor, { allLinks }));
+ }
+ }
+ }
+ // removes all the specified link referneces from the selection.
+ // NOTE: as above, this won't work correctly if there are marks with overlapping but not exact sets of link references.
+ public RemoveLinkFromSelection(allLinks: { href: string, title: string, linkId: string, targetId: string }[]) {
+ const state = this._editorView?.state;
+ if (state && this._editorView) {
+ this._editorView.dispatch(removeMarkWithAttrs(state.tr, state.selection.from, state.selection.to, state.schema.marks.link, { allLinks }));
+ }
+ }
+
linkOnDeselect: Map<string, string> = new Map();
doLinkOnDeselect() {
@@ -181,8 +207,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this.linkOnDeselect.set(key, value);
const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key);
- const allHrefs = [{ href: Utils.prepend("/doc/" + id), title: value, targetId: id }];
- const link = this._editorView.state.schema.marks.link.create({ allHrefs, location: "onRight", title: value });
+ const allLinks = [{ href: Utils.prepend("/doc/" + id), title: value, targetId: id }];
+ const link = this._editorView.state.schema.marks.linkAnchor.create({ allLinks, location: "onRight", title: value });
const mval = this._editorView.state.schema.marks.metadataVal.create();
const offset = (tx.selection.to === range!.end - 1 ? -1 : 0);
tx = tx.addMark(textEndSelection - value.length + offset, textEndSelection, link).addMark(textEndSelection - value.length + offset, textEndSelection, mval);
@@ -250,8 +276,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const lastSel = Math.min(flattened.length - 1, this._searchIndex);
this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
const alink = DocUtils.MakeLink({ doc: this.rootDoc }, { doc: target }, "automatic")!;
- const allHrefs = [{ href: Utils.prepend("/doc/" + alink[Id]), title: "a link", targetId: target[Id], linkId: alink[Id] }];
- const link = this._editorView.state.schema.marks.link.create({ allHrefs, title: "a link", location });
+ const allLinks = [{ href: Utils.prepend("/doc/" + alink[Id]), title: "a link", targetId: target[Id], linkId: alink[Id] }];
+ const link = this._editorView.state.schema.marks.linkAnchor.create({ allLinks, title: "a link", location });
this._editorView.dispatch(tr.addMark(flattened[lastSel].from, flattened[lastSel].to, link));
}
}
@@ -271,7 +297,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
public unhighlightSearchTerms = () => {
- if (this._editorView && (this._editorView as any).docView) {
+ if (window.screen.width < 600) null;
+ else if (this._editorView && (this._editorView as any).docView) {
const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true });
const end = this._editorView.state.doc.nodeSize - 2;
@@ -291,8 +318,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
protected createDropTarget = (ele: HTMLDivElement) => {
this.ProseRef = ele;
- this.dropDisposer?.();
- ele && (this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.layoutDoc));
+ this._dropDisposer?.();
+ ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.layoutDoc));
}
@undoBatch
@@ -618,9 +645,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
let tr = state.tr.addMark(sel.from, sel.to, splitter);
sel.from !== sel.to && tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
if (node.firstChild === null && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) {
- const allHrefs = [{ href, title, targetId, linkId }];
- allHrefs.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.link.name)?.attrs.allHrefs ?? []));
- const link = state.schema.marks.link.create({ allHrefs, title, location, linkId });
+ const allLinks = [{ href, title, targetId, linkId }];
+ allLinks.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.linkAnchor.name)?.attrs.allLinks ?? []));
+ const link = state.schema.marks.linkAnchor.create({ allLinks, title, location, linkId });
tr = tr.addMark(pos, pos + node.nodeSize, link);
}
});
@@ -630,6 +657,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}
componentDidMount() {
+ this._cachedLinks = DocListCast(this.Document.links);
+ this._disposers.links = reaction(() => DocListCast(this.Document.links), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks
+ newLinks => {
+ this._cachedLinks.forEach(l => !newLinks.includes(l) && this.RemoveLinkFromDoc(l));
+ this._cachedLinks = newLinks;
+ });
this._disposers.buttonBar = reaction(
() => DocumentButtonBar.Instance,
instance => {
@@ -660,8 +693,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
incomingValue => {
if (incomingValue !== undefined && this._editorView && !this._applyingChange) {
const updatedState = JSON.parse(incomingValue);
- this._editorView.updateState(EditorState.fromJSON(this.config, updatedState));
- this.tryUpdateHeight();
+ if (JSON.stringify(this._editorView.state.toJSON()) !== JSON.stringify(updatedState)) {
+ this._editorView.updateState(EditorState.fromJSON(this.config, updatedState));
+ this.tryUpdateHeight();
+ }
}
}
);
@@ -733,8 +768,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
return node.copy(content.frag);
}
const marks = [...node.marks];
- const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.link);
- return linkIndex !== -1 && marks[linkIndex].attrs.allHrefs.find((item: { href: string }) => scrollToLinkID === item.href.replace(/.*\/doc\//, "")) ? node : undefined;
+ const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.linkAnchor);
+ return linkIndex !== -1 && marks[linkIndex].attrs.allLinks.find((item: { href: string }) => scrollToLinkID === item.href.replace(/.*\/doc\//, "")) ? node : undefined;
};
let start = 0;
@@ -916,8 +951,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
const marks = [...node.marks];
const linkIndex = marks.findIndex(mark => mark.type.name === "link");
- const allHrefs = [{ href: Utils.prepend(`/doc/${linkId}`), title, linkId }];
- const link = view.state.schema.mark(view.state.schema.marks.link, { allHrefs, location: "onRight", title, docref: true });
+ const allLinks = [{ href: Utils.prepend(`/doc/${linkId}`), title, linkId }];
+ const link = view.state.schema.mark(view.state.schema.marks.linkAnchor, { allLinks, location: "onRight", title, docref: true });
marks.splice(linkIndex === -1 ? 0 : linkIndex, 1, link);
return node.mark(marks);
}
@@ -997,6 +1032,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
_downX = 0;
_downY = 0;
_break = false;
+ _collapsed = false;
onPointerDown = (e: React.PointerEvent): void => {
if (this._recording && !e.ctrlKey && e.button === 0) {
this.stopDictation(true);
@@ -1045,7 +1081,42 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
@action
+ onDoubleClick = (e: React.MouseEvent): void => {
+
+ this.doLinkOnDeselect();
+ FormattedTextBox._downEvent = true;
+ FormattedTextBoxComment.textBox = this;
+ if (this.props.onClick && e.button === 0 && !this.props.isSelected(false)) {
+ e.preventDefault();
+ }
+ if (e.button === 0 && this.props.isSelected(true) && !e.altKey && !e.ctrlKey && !e.metaKey) {
+ if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // stop propagation if not in sidebar
+ e.stopPropagation(); // if the text box is selected, then it consumes all down events
+ }
+ }
+ if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
+ e.preventDefault();
+ }
+ FormattedTextBoxComment.Hide();
+ if (FormattedTextBoxComment.linkDoc) {
+ if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
+ this.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight");
+ } else {
+ DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, this.props.Document,
+ (doc: Doc, followLinkLocation: string) => this.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
+ }
+ }
+
+ (e.nativeEvent as any).formattedHandled = true;
+
+ if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) {
+ e.stopPropagation();
+ }
+ }
+
+ @action
onFocused = (e: React.FocusEvent): void => {
+ console.log("FOUCSS");
FormattedTextBox.FocusedBox = this;
this.tryUpdateHeight();
@@ -1077,9 +1148,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
static _bulletStyleSheet: any = addStyleSheet();
static _userStyleSheet: any = addStyleSheet();
-
+ _forceUncollapse = true; // if the cursor doesn't move between clicks, then the selection will disappear for some reason. This flags the 2nd click as happening on a selection which allows bullet points to toggle
+ _forceDownNode: Node | undefined;
onClick = (e: React.MouseEvent): void => {
- if ((this._editorView!.root as any).getSelection().isCollapsed) { // this is a hack to allow the cursor to be placed at the end of a document when the document ends in an inline dash comment. Apparently Chrome on Windows has a bug/feature which breaks this when clicking after the end of the text.
+ if (Math.abs(e.clientX - this._downX) > 4 || Math.abs(e.clientY - this._downY) > 4) {
+ this._forceDownNode = undefined;
+ return;
+ }
+ if (!this._forceUncollapse || (this._editorView!.root as any).getSelection().isCollapsed) { // this is a hack to allow the cursor to be placed at the end of a document when the document ends in an inline dash comment. Apparently Chrome on Windows has a bug/feature which breaks this when clicking after the end of the text.
const pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
const node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); // get what prosemirror thinks the clicked node is (if it's null, then we didn't click on any text)
if (pcords && node?.type === this._editorView!.state.schema.nodes.dashComment) {
@@ -1091,6 +1167,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
if (e.clientY > lastNode?.getBoundingClientRect().bottom) { // if we clicked below the last prosemirror div, then set the selection to be the end of the document
this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, this._editorView!.state.doc.content.size)));
}
+ } else if ([this._editorView!.state.schema.nodes.ordered_list, this._editorView!.state.schema.nodes.listItem].includes(node?.type) &&
+ node !== (this._editorView!.state.selection as NodeSelection)?.node && pcords) {
+ this._editorView!.dispatch(this._editorView!.state.tr.setSelection(NodeSelection.create(this._editorView!.state.doc, pcords.pos!)));
}
}
if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; }
@@ -1098,12 +1177,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
if (this.props.isSelected(true)) { // if text box is selected, then it consumes all click events
e.stopPropagation();
- this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, false);
+ this.hitBulletTargets(e.clientX, e.clientY, !this._editorView?.state.selection.empty || this._forceUncollapse, false, this._forceDownNode, e.shiftKey);
}
+ this._forceUncollapse = !(this._editorView!.root as any).getSelection().isCollapsed;
+ this._forceDownNode = (this._editorView!.state.selection as NodeSelection)?.node;
}
// this hackiness handles clicking on the list item bullets to do expand/collapse. the bullets are ::before pseudo elements so there's no real way to hit test against them.
- hitBulletTargets(x: number, y: number, collapse: boolean, highlightOnly: boolean) {
+ hitBulletTargets(x: number, y: number, collapse: boolean, highlightOnly: boolean, downNode: Node | undefined = undefined, selectOrderedList: boolean = false) {
+ this._forceUncollapse = false;
clearStyleSheetRules(FormattedTextBox._bulletStyleSheet);
const clickPos = this._editorView!.posAtCoords({ left: x, top: y });
let olistPos = clickPos?.pos;
@@ -1119,20 +1201,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
$olistPos = this._editorView?.state.doc.resolve(($olistPos as any).path[($olistPos as any).path.length - 4]);
}
}
+ const listPos = this._editorView?.state.doc.resolve(clickPos.pos);
const listNode = this._editorView?.state.doc.nodeAt(clickPos.pos);
- if (olistNode && olistNode.type === this._editorView?.state.schema.nodes.ordered_list) {
- if (!collapse) {
- if (!highlightOnly) {
- this._editorView.dispatch(this._editorView.state.tr.setSelection(new NodeSelection($olistPos!)));
- }
- addStyleSheetRule(FormattedTextBox._bulletStyleSheet, olistNode.attrs.mapStyle + olistNode.attrs.bulletStyle + ":hover:before", { background: "lightgray" });
- } else if (listNode && listNode.type === this._editorView.state.schema.nodes.list_item) {
- if (!highlightOnly) {
+ if (olistNode && olistNode.type === this._editorView?.state.schema.nodes.ordered_list && listNode) {
+ if (!highlightOnly) {
+ if (selectOrderedList || (!collapse && listNode.attrs.visibility)) {
+ this._editorView.dispatch(this._editorView.state.tr.setSelection(new NodeSelection(selectOrderedList ? $olistPos! : listPos!)));
+ } else if (!listNode.attrs.visibility || downNode === listNode) {
this._editorView.dispatch(this._editorView.state.tr.setNodeMarkup(clickPos.pos, listNode.type, { ...listNode.attrs, visibility: !listNode.attrs.visibility }));
this._editorView.dispatch(this._editorView.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, clickPos.pos)));
}
- addStyleSheetRule(FormattedTextBox._bulletStyleSheet, olistNode.attrs.mapStyle + olistNode.attrs.bulletStyle + ":hover:before", { background: "lightgray" });
}
+ addStyleSheetRule(FormattedTextBox._bulletStyleSheet, olistNode.attrs.mapStyle + olistNode.attrs.bulletStyle + ":hover:before", { background: "lightgray" });
}
}
}
@@ -1140,7 +1220,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
e.stopPropagation();
const view = this._editorView as any;
- // this interposes on prosemirror's upHandler to prevent prosemirror's up from invoked multiple times when there
+ // this interposes on prosemirror's upHandler to prevent prosemirror's up from invoked multiple times when there
// are nested prosemirrors. We only want the lowest level prosemirror to be invoked.
if (view.mouseDown) {
const originalUpHandler = view.mouseDown.up;
@@ -1163,18 +1243,27 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
});
}
- public static HadSelection: boolean = false;
- onBlur = (e: any) => {
- FormattedTextBox.HadSelection = window.getSelection()?.toString() !== "";
- //DictationManager.Controls.stop(false);
+ public startUndoTypingBatch() {
+ this._undoTyping = UndoManager.StartBatch("undoTyping");
+ }
+
+ public endUndoTypingBatch() {
+ const wasUndoing = this._undoTyping;
if (this._undoTyping) {
this._undoTyping.end();
this._undoTyping = undefined;
}
+ return wasUndoing;
+ }
+ public static HadSelection: boolean = false;
+ onBlur = (e: any) => {
+ FormattedTextBox.HadSelection = window.getSelection()?.toString() !== "";
+ //DictationManager.Controls.stop(false);
+ this.endUndoTypingBatch();
this.doLinkOnDeselect();
// move the richtextmenu offscreen
- if (!RichTextMenu.Instance.Pinned && !RichTextMenu.Instance.overMenu) RichTextMenu.Instance.jumpTo(-300, -300);
+ if (!RichTextMenu.Instance.Pinned) RichTextMenu.Instance.delayHide();
}
_lastTimedMark: Mark | undefined = undefined;
@@ -1208,11 +1297,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
// this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark));
if (!this._undoTyping) {
- this._undoTyping = UndoManager.StartBatch("undoTyping");
+ this.startUndoTypingBatch();
}
}
ondrop = (eve: React.DragEvent) => {
+ this._editorView!.dispatch(updateBullets(this._editorView!.state.tr, this._editorView!.state.schema));
eve.stopPropagation(); // drag n drop of text within text note will generate a new note if not caughst, as will dragging in from outside of Dash.
}
onscrolled = (ev: React.UIEvent) => {
@@ -1280,7 +1370,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
color: this.props.color ? this.props.color : StrCast(this.layoutDoc[this.props.fieldKey + "-color"], this.props.hideOnLeave ? "white" : "inherit"),
pointerEvents: interactive ? undefined : "none",
fontSize: Cast(this.layoutDoc._fontSize, "number", null),
- fontFamily: StrCast(this.layoutDoc._fontFamily, "inherit")
+ fontFamily: StrCast(this.layoutDoc._fontFamily, "inherit"),
+ transition: "opacity 1s"
}}
onContextMenu={this.specificContextMenu}
onKeyDown={this.onKeyPress}
@@ -1302,13 +1393,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}
})}
+ onDoubleClick={this.onDoubleClick}
>
<div className={`formattedTextBox-outer`} ref={this._scrollRef}
style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: !this.props.isSelected() ? "none" : undefined }}
onScroll={this.onscrolled} onDrop={this.ondrop} >
<div className={`formattedTextBox-inner${rounded}${selclass}`} ref={this.createDropTarget}
style={{
- padding: this.layoutDoc._textBoxPadding ? this.layoutDoc._textBoxPadding : `${Math.max(0, NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0) + selPad)}px ${NumCast(this.layoutDoc._xMargin, this.props.xMargin || 0) + selPad}px`,
+ padding: this.layoutDoc._textBoxPadding ? StrCast(this.layoutDoc._textBoxPadding) : `${Math.max(0, NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0) + selPad)}px ${NumCast(this.layoutDoc._xMargin, this.props.xMargin || 0) + selPad}px`,
pointerEvents: !this.props.isSelected() ? ((this.layoutDoc.isLinkButton || this.props.onClick) ? "none" : "all") : undefined
}}
/>
@@ -1348,7 +1440,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
setTimeout(() => this._editorView!.focus(), 500);
e.stopPropagation();
}} >
- <FontAwesomeIcon className="formattedTExtBox-audioFont"
+ <FontAwesomeIcon className="formattedTextBox-audioFont"
style={{ color: this._recording ? "red" : "blue", opacity: this._recording ? 1 : 0.5, display: this.props.isSelected() ? "" : "none" }} icon={"microphone"} size="sm" />
</div>}
</div>
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
index 2dd63ec21..9089e7039 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
@@ -8,10 +8,12 @@
margin-bottom: 7px;
-webkit-transform: translateX(-50%);
transform: translateX(-50%);
- }
- .FormattedTextBox-tooltip:before {
+}
+
+.FormattedTextBox-tooltip:before {
content: "";
- height: 0; width: 0;
+ height: 0;
+ width: 0;
position: absolute;
left: 50%;
margin-left: -5px;
@@ -19,10 +21,12 @@
border: 5px solid transparent;
border-bottom-width: 0;
border-top-color: silver;
- }
- .FormattedTextBox-tooltip:after {
+}
+
+.FormattedTextBox-tooltip:after {
content: "";
- height: 0; width: 0;
+ height: 0;
+ width: 0;
position: absolute;
left: 50%;
margin-left: -5px;
@@ -30,4 +34,72 @@
border: 5px solid transparent;
border-bottom-width: 0;
border-top-color: white;
- } \ No newline at end of file
+}
+
+.FormattedTextBoxComment-buttons {
+ display: none;
+ position: absolute;
+ top: 50%;
+ right: 0;
+ transform: translateY(-50%);
+
+ .FormattedTextBoxComment-button {
+ width: 20px;
+ height: 20px;
+ margin: 0;
+ margin-right: 6px;
+ border-radius: 50%;
+ pointer-events: auto;
+ background-color: rgb(38, 40, 41);
+ color: rgb(178, 181, 184);
+ font-size: 65%;
+ transition: transform 0.2s;
+ text-align: center;
+ position: relative;
+
+ // margin-top: "auto";
+ // margin-bottom: "auto";
+ // background: black;
+ // color: white;
+ // display: inline-block;
+ // border-radius: 18px;
+ // font-size: 12.5px;
+ // width: 18px;
+ // height: 18px;
+ // margin-top: auto;
+ // margin-bottom: auto;
+ // margin-right: 3px;
+ // cursor: pointer;
+ // transition: transform 0.2s;
+
+ .FormattedTextBoxComment-fa-icon {
+ margin-top: "auto";
+ margin-bottom: "auto";
+ background: black;
+ color: white;
+ display: inline-block;
+ border-radius: 18px;
+ font-size: 12.5px;
+ width: 18px;
+ height: 18px;
+ margin-top: auto;
+ margin-bottom: auto;
+ margin-right: 3px;
+ cursor: pointer;
+ transition: transform 0.2s;
+ // position: absolute;
+ // top: 50%;
+ // left: 50%;
+ // transform: translate(-50%, -50%);
+ }
+
+ &:last-child {
+ margin-right: 0;
+ }
+
+ &:hover {
+ background: rgb(53, 146, 199);
+ ;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index 90f2c0aa6..56826e5c7 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -2,7 +2,7 @@ import { Mark, ResolvedPos } from "prosemirror-model";
import { EditorState, Plugin } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import * as ReactDOM from 'react-dom';
-import { Doc, DocCastAsync } from "../../../../fields/Doc";
+import { Doc, DocCastAsync, Opt } from "../../../../fields/Doc";
import { Cast, FieldValue, NumCast } from "../../../../fields/Types";
import { emptyFunction, returnEmptyString, returnFalse, Utils, emptyPath, returnZero, returnOne, returnEmptyFilter } from "../../../../Utils";
import { DocServer } from "../../../DocServer";
@@ -16,6 +16,11 @@ import React = require("react");
import { Docs } from "../../../documents/Documents";
import wiki from "wikijs";
import { DocumentType } from "../../../documents/DocumentTypes";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action } from "mobx";
+import { LinkManager } from "../../../util/LinkManager";
+import { LinkDocPreview } from "../LinkDocPreview";
+import { DocumentLinksButton } from "../DocumentLinksButton";
export let formattedTextBoxCommentPlugin = new Plugin({
view(editorView) { return new FormattedTextBoxComment(editorView); }
@@ -27,7 +32,7 @@ export function findUserMark(marks: Mark[]): Mark | undefined {
return marks.find(m => m.attrs.userid);
}
export function findLinkMark(marks: Mark[]): Mark | undefined {
- return marks.find(m => m.type === schema.marks.link);
+ return marks.find(m => m.type === schema.marks.linkAnchor);
}
export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) {
let before = 0;
@@ -62,6 +67,10 @@ export class FormattedTextBoxComment {
static mark: Mark;
static textBox: FormattedTextBox | undefined;
static linkDoc: Doc | undefined;
+
+ static _deleteRef: Opt<HTMLDivElement | null>;
+ static _followRef: Opt<HTMLDivElement | null>;
+
constructor(view: any) {
if (!FormattedTextBoxComment.tooltip) {
const root = document.getElementById("root");
@@ -75,8 +84,8 @@ export class FormattedTextBoxComment {
FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText);
FormattedTextBoxComment.tooltip.className = "FormattedTextBox-tooltip";
FormattedTextBoxComment.tooltip.style.pointerEvents = "all";
- FormattedTextBoxComment.tooltip.style.maxWidth = "350px";
- FormattedTextBoxComment.tooltip.style.maxHeight = "250px";
+ FormattedTextBoxComment.tooltip.style.maxWidth = "200px";
+ FormattedTextBoxComment.tooltip.style.maxHeight = "206px";
FormattedTextBoxComment.tooltip.style.width = "100%";
FormattedTextBoxComment.tooltip.style.height = "100%";
FormattedTextBoxComment.tooltip.style.overflow = "hidden";
@@ -87,12 +96,25 @@ export class FormattedTextBoxComment {
const textBox = FormattedTextBoxComment.textBox;
if (FormattedTextBoxComment.linkDoc && !keep && textBox) {
if (FormattedTextBoxComment.linkDoc.author) {
- if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
- textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight");
+
+ if (FormattedTextBoxComment._deleteRef && FormattedTextBoxComment._deleteRef.contains(e.target as any)) {
+ this.deleteLink();
+ } else if (FormattedTextBoxComment._followRef && FormattedTextBoxComment._followRef.contains(e.target as any)) {
+ if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
+ textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight");
+ } else {
+ DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document,
+ (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
+ }
} else {
- DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document,
- (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
+ if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
+ textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight");
+ } else {
+ DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document,
+ (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
+ }
}
+
}
} else if (textBox && (FormattedTextBoxComment.tooltipText as any).href) {
textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, _width: 200, _height: 400, UseCors: true }), "onRight");
@@ -106,6 +128,15 @@ export class FormattedTextBoxComment {
}
}
+ @action
+ deleteLink = () => {
+ FormattedTextBoxComment.linkDoc ? LinkManager.Instance.deleteLink(FormattedTextBoxComment.linkDoc) : null;
+ LinkDocPreview.LinkInfo = undefined;
+ DocumentLinksButton.EditLink = undefined;
+ //FormattedTextBoxComment.tooltipText = undefined;
+ FormattedTextBoxComment.Hide();
+ }
+
public static Hide() {
FormattedTextBoxComment.textBox = undefined;
FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none");
@@ -182,7 +213,7 @@ export class FormattedTextBoxComment {
state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node));
child = child || (nbef && state.selection.$from.nodeBefore);
const mark = child ? findLinkMark(child.marks) : undefined;
- const href = (!mark?.attrs.docref || naft === nbef) && mark?.attrs.allHrefs.find((item: { href: string }) => item.href)?.href || forceUrl;
+ const href = (!mark?.attrs.docref || naft === nbef) && mark?.attrs.allLinks.find((item: { href: string }) => item.href)?.href || forceUrl;
if (forceUrl || (href && child && nbef && naft && mark?.attrs.showPreview)) {
FormattedTextBoxComment.tooltipText.textContent = "external => " + href;
(FormattedTextBoxComment.tooltipText as any).href = href;
@@ -210,32 +241,87 @@ export class FormattedTextBoxComment {
}
if (target?.author) {
FormattedTextBoxComment.showCommentbox("", view, nbef);
- ReactDOM.render(<ContentFittingDocumentView
- Document={target}
- LibraryPath={emptyPath}
- fitToBox={true}
- moveDocument={returnFalse}
- rootSelected={returnFalse}
- ScreenToLocalTransform={Transform.Identity}
- parentActive={returnFalse}
- addDocument={returnFalse}
- removeDocument={returnFalse}
- addDocTab={returnFalse}
- pinToPres={returnFalse}
- dontRegisterView={true}
- docFilters={returnEmptyFilter}
- ContainingCollectionDoc={undefined}
- ContainingCollectionView={undefined}
- renderDepth={0}
- PanelWidth={() => Math.min(350, NumCast(target._width, 350))}
- PanelHeight={() => Math.min(250, NumCast(target._height, 250))}
- focus={emptyFunction}
- whenActiveChanged={returnFalse}
- bringToFront={returnFalse}
- ContentScaling={returnOne}
- NativeWidth={returnZero}
- NativeHeight={returnZero}
- />, FormattedTextBoxComment.tooltipText);
+ const docPreview = <div style={{ backgroundColor: "white", border: "8px solid white" }}>
+ {target.title}
+ <div className="wrapper" style={{ float: "right" }}>
+ <div title="Delete link" className="FormattedTextBoxComment-button" style={{
+ display: "inline",
+ paddingLeft: "6px",
+ paddingRight: "6px",
+ paddingTop: "2.5px",
+ paddingBottom: "2.5px",
+ width: "20px",
+ height: "20px",
+ margin: 0,
+ marginRight: "6px",
+ borderRadius: "50%",
+ pointerEvents: "auto",
+ backgroundColor: "rgb(38, 40, 41)",
+ color: "rgb(178, 181, 184)",
+ transition: "transform 0.2s",
+ textAlign: "center",
+ position: "relative"
+ }} ref={(r) => this._deleteRef = r}>
+ <FontAwesomeIcon className="FormattedTextBox-fa-icon" icon="trash"
+ size="sm" /></div>
+ <div title="Follow link" className="FormattedTextBoxComment-button" style={{
+ display: "inline",
+ paddingLeft: "6px",
+ paddingRight: "6px",
+ paddingTop: "2.5px",
+ paddingBottom: "2.5px",
+ width: "20px",
+ height: "20px",
+ margin: 0,
+ marginRight: "6px",
+ borderRadius: "50%",
+ pointerEvents: "auto",
+ backgroundColor: "rgb(38, 40, 41)",
+ color: "rgb(178, 181, 184)",
+ transition: "transform 0.2s",
+ textAlign: "center",
+ position: "relative"
+ }} ref={(r) => this._followRef = r}>
+ <FontAwesomeIcon className="FormattedTextBox-fa-icon" icon="arrow-right"
+ size="sm" />
+ </div>
+ </div>
+ <div className="wrapper" style={{
+ maxWidth: "180px", maxHeight: "168px", overflow: "hidden",
+ overflowY: "hidden", paddingTop: "5px"
+ }}>
+ <ContentFittingDocumentView
+ Document={target}
+ LibraryPath={emptyPath}
+ fitToBox={true}
+ moveDocument={returnFalse}
+ rootSelected={returnFalse}
+ ScreenToLocalTransform={Transform.Identity}
+ parentActive={returnFalse}
+ addDocument={returnFalse}
+ removeDocument={returnFalse}
+ addDocTab={returnFalse}
+ pinToPres={returnFalse}
+ dontRegisterView={true}
+ docFilters={returnEmptyFilter}
+ ContainingCollectionDoc={undefined}
+ ContainingCollectionView={undefined}
+ renderDepth={0}
+ PanelWidth={() => Math.min(350, NumCast(target._width, 350))}
+ PanelHeight={() => Math.min(250, NumCast(target._height, 250))}
+ focus={emptyFunction}
+ whenActiveChanged={returnFalse}
+ bringToFront={returnFalse}
+ ContentScaling={returnOne}
+ NativeWidth={returnZero}
+ NativeHeight={returnZero}
+ />
+ </div>
+ </div>;
+ FormattedTextBoxComment.showCommentbox("", view, nbef);
+
+ ReactDOM.render(docPreview, FormattedTextBoxComment.tooltipText);
+
FormattedTextBoxComment.tooltip.style.width = NumCast(target._width) ? `${NumCast(target._width)}` : "100%";
FormattedTextBoxComment.tooltip.style.height = NumCast(target._height) ? `${NumCast(target._height)}` : "100%";
}
@@ -249,4 +335,4 @@ export class FormattedTextBoxComment {
}
destroy() { }
-}
+} \ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 1bbcb9fa8..9d69f4be7 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -1,7 +1,6 @@
import { chainCommands, exitCode, joinDown, joinUp, lift, deleteSelection, joinBackward, selectNodeBackward, setBlockType, splitBlockKeepMarks, toggleMark, wrapIn, newlineInCode } from "prosemirror-commands";
import { liftTarget } from "prosemirror-transform";
import { redo, undo } from "prosemirror-history";
-import { undoInputRule } from "prosemirror-inputrules";
import { Schema } from "prosemirror-model";
import { liftListItem, sinkListItem } from "./prosemirrorPatches.js";
import { splitListItem, wrapInList, } from "prosemirror-schema-list";
@@ -12,7 +11,6 @@ import { Doc, DataSym } from "../../../../fields/Doc";
import { FormattedTextBox } from "./FormattedTextBox";
import { Id } from "../../../../fields/FieldSymbols";
import { Docs } from "../../../documents/Documents";
-import { update } from "lodash";
const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false;
@@ -215,10 +213,13 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
marks && tx3.setStoredMarks([...marks]);
dispatch(tx3);
})) {
+ const fromattrs = state.selection.$from.node().attrs;
if (!splitBlockKeepMarks(state, (tx3: Transaction) => {
- splitMetadata(marks, tx3);
- if (!liftListItem(schema.nodes.list_item)(tx3, dispatch as ((tx: Transaction<Schema<any, any>>) => void))) {
- dispatch(tx3);
+ const tonode = tx3.selection.$to.node();
+ const tx4 = tx3.setNodeMarkup(tx3.selection.to - 1, tonode.type, fromattrs, tonode.marks);
+ splitMetadata(marks, tx4);
+ if (!liftListItem(schema.nodes.list_item)(tx4, dispatch as ((tx: Transaction<Schema<any, any>>) => void))) {
+ dispatch(tx4);
}
})) {
return false;
@@ -281,19 +282,6 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
return false;
});
- // bind("^", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
- // let newNode = schema.nodes.footnote.create({});
- // if (dispatch && state.selection.from === state.selection.to) {
- // let tr = state.tr;
- // tr.replaceSelectionWith(newNode); // replace insertion with a footnote.
- // dispatch(tr.setSelection(new NodeSelection( // select the footnote node to open its display
- // tr.doc.resolve( // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node)
- // tr.selection.anchor - tr.selection.$anchor.nodeBefore!.nodeSize))));
- // return true;
- // }
- // return false;
- // });
-
return keys;
}
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.scss b/src/client/views/nodes/formattedText/RichTextMenu.scss
index 7a0718c16..fbc468292 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.scss
+++ b/src/client/views/nodes/formattedText/RichTextMenu.scss
@@ -77,6 +77,12 @@
color: white;
}
}
+ .richTextMenu-divider {
+ margin: auto;
+ border-left: solid #ffffff70 0.5px;
+ height: 20px;
+ width: 1px;
+ }
}
.link-menu {
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 839943aac..95d6c9fac 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -1,8 +1,8 @@
import React = require("react");
import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faBold, faCaretDown, faChevronLeft, faEyeDropper, faHighlighter, faIndent, faItalic, faLink, faPaintRoller, faPalette, faStrikethrough, faSubscript, faSuperscript, faUnderline } from "@fortawesome/free-solid-svg-icons";
+import { faBold, faCaretDown, faChevronLeft, faEyeDropper, faHighlighter, faOutdent, faIndent, faHandPointLeft, faHandPointRight, faItalic, faLink, faPaintRoller, faPalette, faStrikethrough, faSubscript, faSuperscript, faUnderline } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, observable } from "mobx";
+import { action, observable, IReactionDisposer, reaction } from "mobx";
import { observer } from "mobx-react";
import { lift, wrapIn } from "prosemirror-commands";
import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos } from "prosemirror-model";
@@ -23,9 +23,10 @@ import { updateBullets } from "./ProsemirrorExampleTransfer";
import "./RichTextMenu.scss";
import { schema } from "./schema_rts";
import { TraceMobx } from "../../../../fields/util";
+import { UndoManager } from "../../../util/UndoManager";
const { toggleMark } = require("prosemirror-commands");
-library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller);
+library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faOutdent, faIndent, faHandPointLeft, faHandPointRight, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller);
@observer
@@ -68,6 +69,8 @@ export default class RichTextMenu extends AntimodeMenu {
@observable private currentLink: string | undefined = "";
@observable private showLinkDropdown: boolean = false;
+ _reaction: IReactionDisposer | undefined;
+ _delayHide = false;
constructor(props: Readonly<{}>) {
super(props);
RichTextMenu.Instance = this;
@@ -138,6 +141,16 @@ export default class RichTextMenu extends AntimodeMenu {
];
}
+ componentDidMount() {
+ this._reaction = reaction(() => SelectionManager.SelectedDocuments(),
+ () => this._delayHide && !(this._delayHide = false) && this.fadeOut(true));
+ }
+ componentWillUnmount() {
+ this._reaction?.();
+ }
+
+ public delayHide = () => this._delayHide = true;
+
@action
changeView(view: EditorView) {
this.view = view;
@@ -147,16 +160,6 @@ export default class RichTextMenu extends AntimodeMenu {
this.updateFromDash(view, lastState, this.editorProps);
}
- public MakeLinkToSelection = (linkDocId: string, title: string, location: string, targetDocId: string): string => {
- if (this.view) {
- const link = this.view.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, linkId: linkDocId, targetId: targetDocId });
- this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link).
- addMark(this.view.state.selection.from, this.view.state.selection.to, link));
- return this.view.state.selection.$from.nodeAfter?.text || "";
- }
- return "";
- }
-
@action
public async updateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) {
if (!view) {
@@ -310,8 +313,11 @@ export default class RichTextMenu extends AntimodeMenu {
function onClick(e: React.PointerEvent) {
e.preventDefault();
e.stopPropagation();
- self.view && command && command(self.view.state, self.view.dispatch, self.view);
- self.view && onclick && onclick(self.view.state, self.view.dispatch, self.view);
+ self.TextView.endUndoTypingBatch();
+ UndoManager.RunInBatch(() => {
+ self.view && command && command(self.view.state, self.view.dispatch, self.view);
+ self.view && onclick && onclick(self.view.state, self.view.dispatch, self.view);
+ }, "rich text menu command");
self.setActiveMarkButtons(self.getActiveMarksOnSelection());
}
@@ -338,9 +344,10 @@ export default class RichTextMenu extends AntimodeMenu {
function onChange(e: React.ChangeEvent<HTMLSelectElement>) {
e.stopPropagation();
e.preventDefault();
+ self.TextView.endUndoTypingBatch();
options.forEach(({ label, mark, command }) => {
if (e.target.value === label) {
- self.view && mark && command(mark, self.view);
+ UndoManager.RunInBatch(() => self.view && mark && command(mark, self.view), "text mark dropdown");
}
});
}
@@ -361,9 +368,10 @@ export default class RichTextMenu extends AntimodeMenu {
const self = this;
function onChange(val: string) {
+ self.TextView.endUndoTypingBatch();
options.forEach(({ label, node, command }) => {
if (val === label) {
- self.view && node && command(node);
+ UndoManager.RunInBatch(() => self.view && node && command(node), "nodes dropdown");
}
});
}
@@ -412,6 +420,85 @@ export default class RichTextMenu extends AntimodeMenu {
dispatch?.(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
return true;
}
+ alignCenter = (state: EditorState<any>, dispatch: any) => {
+ return this.alignParagraphs(state, "center", dispatch);
+ }
+ alignLeft = (state: EditorState<any>, dispatch: any) => {
+ return this.alignParagraphs(state, "left", dispatch);
+ }
+ alignRight = (state: EditorState<any>, dispatch: any) => {
+ return this.alignParagraphs(state, "right", dispatch);
+ }
+
+ alignParagraphs(state: EditorState<any>, align: "left" | "right" | "center", dispatch: any) {
+ var tr = state.tr;
+ state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
+ if (node.type === schema.nodes.paragraph) {
+ tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, align }, node.marks);
+ return false;
+ }
+ return true;
+ });
+ dispatch?.(tr);
+ return true;
+ }
+
+ insetParagraph(state: EditorState<any>, dispatch: any) {
+ var tr = state.tr;
+ state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
+ if (node.type === schema.nodes.paragraph) {
+ const inset = (node.attrs.inset ? Number(node.attrs.inset) : 0) + 10;
+ tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, inset }, node.marks);
+ return false;
+ }
+ return true;
+ });
+ dispatch?.(tr);
+ return true;
+ }
+ outsetParagraph(state: EditorState<any>, dispatch: any) {
+ var tr = state.tr;
+ state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
+ if (node.type === schema.nodes.paragraph) {
+ const inset = Math.max(0, (node.attrs.inset ? Number(node.attrs.inset) : 0) - 10);
+ tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, inset }, node.marks);
+ return false;
+ }
+ return true;
+ });
+ dispatch?.(tr);
+ return true;
+ }
+
+ indentParagraph(state: EditorState<any>, dispatch: any) {
+ var tr = state.tr;
+ state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
+ if (node.type === schema.nodes.paragraph) {
+ const nodeval = node.attrs.indent ? Number(node.attrs.indent) : undefined;
+ const indent = !nodeval ? 25 : nodeval < 0 ? 0 : nodeval + 25;
+ tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, indent }, node.marks);
+ return false;
+ }
+ return true;
+ });
+ dispatch?.(tr);
+ return true;
+ }
+
+ hangingIndentParagraph(state: EditorState<any>, dispatch: any) {
+ var tr = state.tr;
+ state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
+ if (node.type === schema.nodes.paragraph) {
+ const nodeval = node.attrs.indent ? Number(node.attrs.indent) : undefined;
+ const indent = !nodeval ? -25 : nodeval > 0 ? 0 : nodeval - 10;
+ tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, indent }, node.marks);
+ return false;
+ }
+ return true;
+ });
+ dispatch?.(tr);
+ return true;
+ }
insertBlockquote(state: EditorState<any>, dispatch: any) {
const path = (state.selection.$from as any).path;
@@ -423,6 +510,11 @@ export default class RichTextMenu extends AntimodeMenu {
return true;
}
+ insertHorizontalRule(state: EditorState<any>, dispatch: any) {
+ dispatch(state.tr.replaceSelectionWith(state.schema.nodes.horizontal_rule.create()).scrollIntoView());
+ return true;
+ }
+
@action toggleBrushDropdown() { this.showBrushDropdown = !this.showBrushDropdown; }
// todo: add brushes to brushMap to save with a style name
@@ -439,7 +531,8 @@ export default class RichTextMenu extends AntimodeMenu {
function onBrushClick(e: React.PointerEvent) {
e.preventDefault();
e.stopPropagation();
- self.view && self.fillBrush(self.view.state, self.view.dispatch);
+ self.TextView.endUndoTypingBatch();
+ UndoManager.RunInBatch(() => self.view && self.fillBrush(self.view.state, self.view.dispatch), "rt brush");
}
let label = "Stored marks: ";
@@ -506,19 +599,24 @@ export default class RichTextMenu extends AntimodeMenu {
@action toggleColorDropdown() { this.showColorDropdown = !this.showColorDropdown; }
@action setActiveColor(color: string) { this.activeFontColor = color; }
+ get TextView() { return (this.view as any).TextView as FormattedTextBox; }
createColorButton() {
const self = this;
function onColorClick(e: React.PointerEvent) {
e.preventDefault();
e.stopPropagation();
- self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch);
+ self.TextView.endUndoTypingBatch();
+ UndoManager.RunInBatch(() => self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch), "rt menu color");
+ self.TextView.EditorView!.focus();
}
function changeColor(e: React.PointerEvent, color: string) {
e.preventDefault();
e.stopPropagation();
self.setActiveColor(color);
- self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch);
+ self.TextView.endUndoTypingBatch();
+ UndoManager.RunInBatch(() => self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch), "rt menu color");
+ self.TextView.EditorView!.focus();
}
const button =
@@ -563,13 +661,15 @@ export default class RichTextMenu extends AntimodeMenu {
function onHighlightClick(e: React.PointerEvent) {
e.preventDefault();
e.stopPropagation();
- self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch);
+ self.TextView.endUndoTypingBatch();
+ UndoManager.RunInBatch(() => self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch), "rt highligher");
}
function changeHighlight(e: React.PointerEvent, color: string) {
e.preventDefault();
e.stopPropagation();
self.setActiveHighlight(color);
- self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch);
+ self.TextView.endUndoTypingBatch();
+ UndoManager.RunInBatch(() => self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch), "rt highlighter");
}
const button =
@@ -609,7 +709,8 @@ export default class RichTextMenu extends AntimodeMenu {
const self = this;
function onLinkChange(e: React.ChangeEvent<HTMLInputElement>) {
- self.setCurrentLink(e.target.value);
+ self.TextView.endUndoTypingBatch();
+ UndoManager.RunInBatch(() => self.setCurrentLink(e.target.value), "link change");
}
const link = this.currentLink ? this.currentLink : "";
@@ -636,7 +737,7 @@ export default class RichTextMenu extends AntimodeMenu {
const node = this.view.state.selection.$from.nodeAfter;
const link = node && node.marks.find(m => m.type.name === "link");
if (link) {
- const href = link.attrs.allHrefs.length > 0 ? link.attrs.allHrefs[0].href : undefined;
+ const href = link.attrs.allLinks.length > 0 ? link.attrs.allLinks[0].href : undefined;
if (href) {
if (href.indexOf(Utils.prepend("/doc/")) === 0) {
const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
@@ -671,40 +772,28 @@ export default class RichTextMenu extends AntimodeMenu {
}
deleteLink = () => {
- if (!this.view) return;
-
- const node = this.view.state.selection.$from.nodeAfter;
- const link = node && node.marks.find(m => m.type === this.view!.state.schema.marks.link);
- const href = link!.attrs.allHrefs.length > 0 ? link!.attrs.allHrefs[0].href : undefined;
- if (href) {
- if (href.indexOf(Utils.prepend("/doc/")) === 0) {
- const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
- if (linkclicked) {
- DocServer.GetRefField(linkclicked).then(async linkDoc => {
- if (linkDoc instanceof Doc) {
- LinkManager.Instance.deleteLink(linkDoc);
- this.view!.dispatch(this.view!.state.tr.removeMark(this.view!.state.selection.from, this.view!.state.selection.to, this.view!.state.schema.marks.link));
- }
- });
- }
- } else {
- if (node) {
- const { tr, schema, selection } = this.view.state;
- const extension = this.linkExtend(selection.$anchor, href);
- this.view.dispatch(tr.removeMark(extension.from, extension.to, schema.marks.link));
- }
+ if (this.view) {
+ const link = this.view.state.selection.$from.nodeAfter?.marks.find(m => m.type === this.view!.state.schema.marks.linkAnchor);
+ if (link) {
+ const allLinks = link.attrs.allLinks.slice();
+ this.TextView.RemoveLinkFromSelection(link.attrs.allLinks);
+ // bcz: Argh ... this will remove the link from the document even it's anchored somewhere else in the text which happens if only part of the anchor text was selected.
+ allLinks.filter((aref: any) => aref?.href.indexOf(Utils.prepend("/doc/")) === 0).forEach((aref: any) => {
+ const linkId = aref.href.replace(Utils.prepend("/doc/"), "").split("?")[0];
+ linkId && DocServer.GetRefField(linkId).then(linkDoc => LinkManager.Instance.deleteLink(linkDoc as Doc));
+ });
}
}
}
linkExtend($start: ResolvedPos, href: string) {
- const mark = this.view!.state.schema.marks.link;
+ const mark = this.view!.state.schema.marks.linkAnchor;
let startIndex = $start.index();
let endIndex = $start.indexAfter();
- while (startIndex > 0 && $start.parent.child(startIndex - 1).marks.filter(m => m.type === mark && m.attrs.allHrefs.find((item: { href: string }) => item.href === href)).length) startIndex--;
- while (endIndex < $start.parent.childCount && $start.parent.child(endIndex).marks.filter(m => m.type === mark && m.attrs.allHrefs.find((item: { href: string }) => item.href === href)).length) endIndex++;
+ while (startIndex > 0 && $start.parent.child(startIndex - 1).marks.filter(m => m.type === mark && m.attrs.allLinks.find((item: { href: string }) => item.href === href)).length) startIndex--;
+ while (endIndex < $start.parent.childCount && $start.parent.child(endIndex).marks.filter(m => m.type === mark && m.attrs.allLinks.find((item: { href: string }) => item.href === href)).length) endIndex++;
let startPos = $start.start();
let endPos = startPos;
@@ -744,7 +833,7 @@ export default class RichTextMenu extends AntimodeMenu {
return ref_node;
}
- @action onPointerEnter(e: React.PointerEvent) { RichTextMenu.Instance.overMenu = true; }
+ @action onPointerEnter(e: React.PointerEvent) { RichTextMenu.Instance.overMenu = false; }
@action onPointerLeave(e: React.PointerEvent) { RichTextMenu.Instance.overMenu = false; }
@action
@@ -768,26 +857,41 @@ export default class RichTextMenu extends AntimodeMenu {
TraceMobx();
const row1 = <div className="antimodeMenu-row" key="row1" style={{ display: this.collapsed ? "none" : undefined }}>{[
!this.collapsed ? this.getDragger() : (null),
- this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)),
- this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)),
- this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)),
- this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)),
- this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)),
- this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)),
+ !this.Pinned ? (null) : <> {[
+ this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)),
+ this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)),
+ this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)),
+ this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)),
+ this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)),
+ this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)),
+ <div className="richTextMenu-divider" />
+ ]}</>,
this.createColorButton(),
this.createHighlighterButton(),
this.createLinkButton(),
this.createBrushButton(),
- this.createButton("sort-amount-down", "Summarize", undefined, this.insertSummarizer),
- this.createButton("quote-left", "Blockquote", undefined, this.insertBlockquote),
+ <div className="richTextMenu-divider" />,
+ this.createButton("align-left", "Align Left", undefined, this.alignLeft),
+ this.createButton("align-center", "Align Center", undefined, this.alignCenter),
+ this.createButton("align-right", "Align Right", undefined, this.alignRight),
+ this.createButton("indent", "Inset More", undefined, this.insetParagraph),
+ this.createButton("outdent", "Inset Less", undefined, this.outsetParagraph),
+ this.createButton("hand-point-left", "Hanging Indent", undefined, this.hangingIndentParagraph),
+ this.createButton("hand-point-right", "Indent", undefined, this.indentParagraph),
]}</div>;
const row2 = <div className="antimodeMenu-row row-2" key="antimodemenu row2">
{this.collapsed ? this.getDragger() : (null)}
<div key="row" style={{ display: this.collapsed ? "none" : undefined }}>
+ <div className="richTextMenu-divider" />,
{[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size"),
this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family"),
- this.createNodesDropdown(this.activeListType, this.listTypeOptions, "nodes")]}
+ <div className="richTextMenu-divider" />,
+ this.createNodesDropdown(this.activeListType, this.listTypeOptions, "nodes"),
+ this.createButton("sort-amount-down", "Summarize", undefined, this.insertSummarizer),
+ this.createButton("quote-left", "Blockquote", undefined, this.insertBlockquote),
+ this.createButton("minus", "Horizontal Rule", undefined, this.insertHorizontalRule),
+ <div className="richTextMenu-divider" />,]}
</div>
<div key="button">
{/* <div key="collapser">
@@ -817,7 +921,7 @@ interface ButtonDropdownProps {
}
@observer
-class ButtonDropdown extends React.Component<ButtonDropdownProps> {
+export class ButtonDropdown extends React.Component<ButtonDropdownProps> {
@observable private showDropdown: boolean = false;
private ref: HTMLDivElement | null = null;
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 6a85e3b7c..ca30dde9d 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -279,7 +279,7 @@ export class RichTextRules {
DocUtils.Publish(target, docid, returnFalse, returnFalse);
DocUtils.MakeLink({ doc: this.Document }, { doc: target }, "portal to");
});
- const link = state.schema.marks.link.create({ href: Utils.prepend("/doc/" + docid), location: "onRight", title: docid, targetId: docid });
+ const link = state.schema.marks.linkAnchor.create({ href: Utils.prepend("/doc/" + docid), location: "onRight", title: docid, targetId: docid });
return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 2).addMark(start, end - 3, link);
}
return state.tr;
@@ -315,8 +315,6 @@ export class RichTextRules {
return node ? state.tr.replaceRangeWith(start, end, dashDoc).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
}),
-
-
// create an inline view of a tag stored under the '#' field
new InputRule(
new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_;\-0-9]*)\s$/),
@@ -374,7 +372,6 @@ export class RichTextRules {
new InputRule(
new RegExp(/%\)/),
(state, match, start, end) => {
-
return state.tr.deleteRange(start, end).removeStoredMark(state.schema.marks.summarizeInclusive.create());
}),
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index 54b61aa20..3d7d71b14 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -17,12 +17,12 @@ export const marks: { [index: string]: MarkSpec } = {
return ["div", { className: "dummy" }, 0];
}
},
- // :: MarkSpec A link. Has `href` and `title` attributes. `title`
+ // :: MarkSpec A linkAnchor. The anchor can have multiple links, where each link has an href URL and a title for use in menus and hover (Dash links have linkIDs & targetIDs). `title`
// defaults to the empty string. Rendered and parsed as an `<a>`
// element.
- link: {
+ linkAnchor: {
attrs: {
- allHrefs: { default: [] as { href: string, title: string, linkId: string, targetId: string }[] },
+ allLinks: { default: [] as { href: string, title: string, linkId: string, targetId: string }[] },
showPreview: { default: true },
location: { default: null },
title: { default: null },
@@ -31,22 +31,22 @@ export const marks: { [index: string]: MarkSpec } = {
inclusive: false,
parseDOM: [{
tag: "a[href]", getAttrs(dom: any) {
- return { allHrefs: [{ href: dom.getAttribute("href"), title: dom.getAttribute("title"), linkId: dom.getAttribute("linkids"), targetId: dom.getAttribute("targetids") }], location: dom.getAttribute("location"), };
+ return { allLinks: [{ href: dom.getAttribute("href"), title: dom.getAttribute("title"), linkId: dom.getAttribute("linkids"), targetId: dom.getAttribute("targetids") }], location: dom.getAttribute("location"), };
}
}],
toDOM(node: any) {
- const targetids = node.attrs.allHrefs.reduce((p: string, item: { href: string, title: string, targetId: string, linkId: string }) => p + " " + item.targetId, "");
- const linkids = node.attrs.allHrefs.reduce((p: string, item: { href: string, title: string, targetId: string, linkId: string }) => p + " " + item.linkId, "");
+ const targetids = node.attrs.allLinks.reduce((p: string, item: { href: string, title: string, targetId: string, linkId: string }) => p + " " + item.targetId, "");
+ const linkids = node.attrs.allLinks.reduce((p: string, item: { href: string, title: string, targetId: string, linkId: string }) => p + " " + item.linkId, "");
return node.attrs.docref && node.attrs.title ?
- ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, href: node.attrs.allHrefs[0].href, class: "prosemirror-attribution" }, node.attrs.title], ["br"]] :
- node.attrs.allHrefs.length === 1 ?
- ["a", { ...node.attrs, class: linkids, targetids, title: `${node.attrs.title}`, href: node.attrs.allHrefs[0].href }, 0] :
+ ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, href: node.attrs.allLinks[0].href, class: "prosemirror-attribution" }, node.attrs.title], ["br"]] :
+ node.attrs.allLinks.length === 1 ?
+ ["a", { ...node.attrs, class: linkids, targetids, title: `${node.attrs.title}`, href: node.attrs.allLinks[0].href }, 0] :
["div", { class: "prosemirror-anchor" },
["span", { class: "prosemirror-linkBtn" },
["a", { ...node.attrs, class: linkids, targetids, title: `${node.attrs.title}` }, 0],
["input", { class: "prosemirror-hrefoptions" }],
],
- ["div", { class: "prosemirror-links" }, ...node.attrs.allHrefs.map((item: { href: string, title: string }) =>
+ ["div", { class: "prosemirror-links" }, ...node.attrs.allLinks.map((item: { href: string, title: string }) =>
["a", { class: "prosemirror-dropdownlink", href: item.href }, item.title]
)]
];
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index afb1f57b7..1af821738 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -310,9 +310,9 @@ export const nodes: { [index: string]: NodeSpec } = {
}],
toDOM(node: any) {
const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : "";
- return node.attrs.visibility ?
- ["li", { class: `${map}`, "data-mapStyle": node.attrs.mapStyle, "data-bulletStyle": node.attrs.bulletStyle }, 0] :
- ["li", { class: `${map}`, "data-mapStyle": node.attrs.mapStyle, "data-bulletStyle": node.attrs.bulletStyle }, "..."];
+ return ["li", { class: `${map}`, "data-mapStyle": node.attrs.mapStyle, "data-bulletStyle": node.attrs.bulletStyle }, node.attrs.visibility ? 0 :
+ ["span", { style: `position: relative; width: 100%; height: 1.5em; overflow: hidden; display: ${node.attrs.mapStyle !== "bullet" ? "inline-block" : "list-item"}; text-overflow: ellipsis; white-space: pre` },
+ `${node.firstChild?.textContent}...`]];
}
},
}; \ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/prosemirrorPatches.js b/src/client/views/nodes/formattedText/prosemirrorPatches.js
index 763961958..0969ea4ef 100644
--- a/src/client/views/nodes/formattedText/prosemirrorPatches.js
+++ b/src/client/views/nodes/formattedText/prosemirrorPatches.js
@@ -9,6 +9,7 @@ var prosemirrorModel = require('prosemirror-model');
exports.liftListItem = liftListItem;
exports.sinkListItem = sinkListItem;
exports.wrappingInputRule = wrappingInputRule;
+exports.removeMarkWithAttrs = removeMarkWithAttrs;
// :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool
// Create a command to lift the list item around the selection up into
// a wrapping list.
@@ -139,3 +140,57 @@ function wrappingInputRule(regexp, nodeType, getAttrs, joinPredicate, customWith
}
+// :: ([Mark]) → ?Mark
+// Tests whether there is a mark of this type in the given set.
+function isInSetWithAttrs(mark, set, attrs) {
+ for (var i = 0; i < set.length; i++) {
+ if (set[i].type == mark) {
+ if (Array.from(Object.keys(attrs)).reduce((p, akey) => {
+ return p && JSON.stringify(set[i].attrs[akey]) === JSON.stringify(attrs[akey]);
+ }, true)) {
+ return set[i];
+ }
+ }
+ }
+};
+
+// :: (number, number, ?union<Mark, MarkType>) → this
+// Remove marks from inline nodes between `from` and `to`. When `mark`
+// is a single mark, remove precisely that mark. When it is a mark type,
+// remove all marks of that type. When it is null, remove all marks of
+// any type.
+function removeMarkWithAttrs(tr, from, to, mark, attrs) {
+ if (mark === void 0) mark = null;
+
+ var matched = [], step = 0;
+ tr.doc.nodesBetween(from, to, function (node, pos) {
+ if (!node.isInline) { return }
+ step++;
+ var toRemove = null;
+ if (mark) {
+ if (isInSetWithAttrs(mark, node.marks, attrs)) { toRemove = [mark]; }
+ } else {
+ toRemove = node.marks;
+ }
+ if (toRemove && toRemove.length) {
+ var end = Math.min(pos + node.nodeSize, to);
+ for (var i = 0; i < toRemove.length; i++) {
+ var style = toRemove[i], found$1 = (void 0);
+ for (var j = 0; j < matched.length; j++) {
+ var m = matched[j];
+ if (m.step == step - 1 && style.eq(matched[j].style)) { found$1 = m; }
+ }
+ if (found$1) {
+ found$1.to = end;
+ found$1.step = step;
+ } else {
+ matched.push({ style: style, from: Math.max(pos, from), to: end, step: step });
+ }
+ }
+ }
+ });
+ matched.forEach(function (m) { return tr.step(new prosemirrorTransform.RemoveMarkStep(m.from, m.to, m.style)); });
+ return tr
+};
+
+
diff --git a/src/client/views/pdf/PDFMenu.scss b/src/client/views/pdf/PDFMenu.scss
index 3c08ba80d..fa43a99b2 100644
--- a/src/client/views/pdf/PDFMenu.scss
+++ b/src/client/views/pdf/PDFMenu.scss
@@ -3,4 +3,23 @@
width: 200px;
padding: 5px;
grid-template-columns: 90px 20px 90px;
+}
+
+.color-wrapper {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+
+ button.color-button {
+ width: 20px;
+ height: 20px;
+ border-radius: 15px !important;
+ margin: 3px;
+ border: 2px solid transparent !important;
+ padding: 3px;
+
+ &.active {
+ border: 2px solid white;
+ }
+ }
} \ No newline at end of file
diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx
index 6dcf5cce6..00c56d73e 100644
--- a/src/client/views/pdf/PDFMenu.tsx
+++ b/src/client/views/pdf/PDFMenu.tsx
@@ -1,22 +1,43 @@
import React = require("react");
import "./PDFMenu.scss";
-import { observable, action, } from "mobx";
+import { observable, action, computed, } from "mobx";
import { observer } from "mobx-react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { unimplementedFunction, returnFalse } from "../../../Utils";
+import { unimplementedFunction, returnFalse, Utils } from "../../../Utils";
import AntimodeMenu from "../AntimodeMenu";
import { Doc, Opt } from "../../../fields/Doc";
+import { ColorState } from "react-color";
+import { ButtonDropdown } from "../nodes/formattedText/RichTextMenu";
+
@observer
export default class PDFMenu extends AntimodeMenu {
static Instance: PDFMenu;
private _commentCont = React.createRef<HTMLButtonElement>();
+ private _palette = [
+ "rgba(208, 2, 27, 0.8)",
+ "rgba(238, 0, 0, 0.8)",
+ "rgba(245, 166, 35, 0.8)",
+ "rgba(248, 231, 28, 0.8)",
+ "rgba(245, 230, 95, 0.616)",
+ "rgba(139, 87, 42, 0.8)",
+ "rgba(126, 211, 33, 0.8)",
+ "rgba(65, 117, 5, 0.8)",
+ "rgba(144, 19, 254, 0.8)",
+ "rgba(238, 169, 184, 0.8)",
+ "rgba(224, 187, 228, 0.8)",
+ "rgba(225, 223, 211, 0.8)",
+ "rgba(255, 255, 255, 0.8)",
+ "rgba(155, 155, 155, 0.8)",
+ "rgba(0, 0, 0, 0.8)"];
@observable private _keyValue: string = "";
@observable private _valueValue: string = "";
@observable private _added: boolean = false;
+ @observable private highlightColor: string = "rgba(245, 230, 95, 0.616)";
+ @observable public _colorBtn = false;
@observable public Highlighting: boolean = false;
@observable public Status: "pdf" | "annotation" | "" = "";
@@ -70,11 +91,47 @@ export default class PDFMenu extends AntimodeMenu {
@action
highlightClicked = (e: React.MouseEvent) => {
- if (!this.Highlight("rgba(245, 230, 95, 0.616)") && this.Pinned) { // yellowish highlight color for a marker type highlight
+ if (!this.Highlight(this.highlightColor) && this.Pinned) {
this.Highlighting = !this.Highlighting;
}
}
+ @computed get highlighter() {
+ const button =
+ <button className="antimodeMenu-button color-preview-button" title="" key="highilghter-button" onPointerDown={this.highlightClicked}>
+ <FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: "transform 0.1s", transform: this.Highlighting ? "" : "rotate(-45deg)" }} />
+ <div className="color-preview" style={{ backgroundColor: this.highlightColor }}></div>
+ </button>;
+
+ const dropdownContent =
+ <div className="dropdown">
+ <p>Change highlighter color:</p>
+ <div className="color-wrapper">
+ {this._palette.map(color => {
+ if (color) {
+ return this.highlightColor === color ?
+ <button className="color-button active" key={`active ${color}`} style={{ backgroundColor: color }} onPointerDown={e => this.changeHighlightColor(color, e)}></button> :
+ <button className="color-button" key={`inactive ${color}`} style={{ backgroundColor: color }} onPointerDown={e => this.changeHighlightColor(color, e)}></button>;
+ }
+ })}
+ </div>
+ </div>;
+ return (
+ <ButtonDropdown key={"highlighter"} button={button} dropdownContent={dropdownContent} />
+ );
+ }
+
+ @action
+ changeHighlightColor = (color: string, e: React.PointerEvent) => {
+ const col: ColorState = {
+ hex: color, hsl: { a: 0, h: 0, s: 0, l: 0, source: "" }, hsv: { a: 0, h: 0, s: 0, v: 0, source: "" },
+ rgb: { a: 0, r: 0, b: 0, g: 0, source: "" }, oldHue: 0, source: "",
+ };
+ e.preventDefault();
+ e.stopPropagation();
+ this.highlightColor = Utils.colorString(col);
+ }
+
deleteClicked = (e: React.PointerEvent) => {
this.Delete();
}
@@ -101,12 +158,11 @@ export default class PDFMenu extends AntimodeMenu {
render() {
const buttons = this.Status === "pdf" ?
[
- <button key="1" className="antimodeMenu-button" title="Click to Highlight" onClick={this.highlightClicked} style={this.Highlighting ? { backgroundColor: "#121212" } : {}}>
- <FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: "transform 0.1s", transform: this.Highlighting ? "" : "rotate(-45deg)" }} /></button>,
+ this.highlighter,
<button key="2" className="antimodeMenu-button" title="Drag to Annotate" ref={this._commentCont} onPointerDown={this.pointerDown}>
<FontAwesomeIcon icon="comment-alt" size="lg" /></button>,
<button key="4" className="antimodeMenu-button" title="Pin Menu" onClick={this.togglePin} style={this.Pinned ? { backgroundColor: "#121212" } : {}}>
- <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transition: "transform 0.1s", transform: this.Pinned ? "rotate(45deg)" : "" }} /> </button>
+ <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transition: "transform 0.1s", transform: this.Pinned ? "rotate(45deg)" : "" }} /></button>,
] : [
<button key="5" className="antimodeMenu-button" title="Delete Anchor" onPointerDown={this.deleteClicked}>
<FontAwesomeIcon icon="trash-alt" size="lg" /></button>,
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 372d01b9c..4e5fdbfbb 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -97,7 +97,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
@observable private _zoomed = 1;
private _pdfViewer: any;
- private _retries = 0; // number of times tried to create the PDF viewer
+ private _retries = 0; // number of times tried to create the PDF viewer
private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void);
private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
private _reactionDisposer?: IReactionDisposer;
@@ -114,8 +114,8 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
private _downX: number = 0;
private _downY: number = 0;
private _coverPath: any;
+ private _lastSearch = false;
private _viewerIsSetup = false;
- private _lastSearch: string = "";
@computed get allAnnotations() {
return DocListCast(this.dataDoc[this.props.fieldKey + "-annotations"]).
@@ -149,16 +149,11 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
runInAction(() => this._showWaiting = this._showCover = true);
this.props.startupLive && this.setupPdfJsViewer();
this._mainCont.current && (this._mainCont.current.scrollTop = this.layoutDoc._scrollTop || 0);
- this._searchReactionDisposer = reaction(() => this.Document.searchMatch, search => {
- if (search) {
- this.search(Doc.SearchQuery(), true);
- this._lastSearch = Doc.SearchQuery();
- }
- else {
- setTimeout(() => this._lastSearch === "mxytzlaf" && this.search("mxytzlaf", true), 200); // bcz: how do we clear search highlights?
- this._lastSearch && (this._lastSearch = "mxytzlaf");
- }
- }, { fireImmediately: true });
+ this._searchReactionDisposer = reaction(() => this.Document.searchMatch,
+ m => {
+ if (m) (this._lastSearch = true) && this.search(Doc.SearchQuery(), true);
+ else !(this._lastSearch = false) && setTimeout(() => !this._lastSearch && this.search("", false, true), 200);
+ }, { fireImmediately: true });
this._selectionReactionDisposer = reaction(() => this.props.isSelected(),
selected => {
@@ -395,11 +390,12 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
}
@action
- search = (searchString: string, fwd: boolean) => {
- if (!searchString) {
+ search = (searchString: string, fwd: boolean, clear: boolean = false) => {
+ if (clear) {
+ this._pdfViewer.findController.executeCommand('reset', {});
+ } else if (!searchString) {
fwd ? this.nextAnnotation() : this.prevAnnotation();
- }
- else if (this._pdfViewer.pageViewsReady) {
+ } else if (this._pdfViewer.pageViewsReady) {
this._pdfViewer.findController.executeCommand('findagain', {
caseSensitive: false,
findPrevious: !fwd,
@@ -525,7 +521,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
if (this._marqueeing) {
if (this._marqueeWidth > 10 || this._marqueeHeight > 10) {
const marquees = this._mainCont.current!.getElementsByClassName("pdfViewerDash-dragAnnotationBox");
- if (marquees && marquees.length) { // copy the marquee and convert it to a permanent annotation.
+ if (marquees && marquees.length) { // copy the marquee and convert it to a permanent annotation.
const style = (marquees[0] as HTMLDivElement).style;
const copy = document.createElement("div");
copy.style.left = style.left;
@@ -695,7 +691,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
</div>;
}
@computed get pdfViewerDiv() {
- return <div className={"pdfViewerDash-text" + ((!DocumentDecorations.Instance.Interacting && (this.props.isSelected() || this.props.isChildActive())) ? "-selected" : "")} ref={this._viewer} />;
+ return <div className={"pdfViewerDash-text" + ((!DocumentDecorations.Instance?.Interacting && (this.props.isSelected() || this.props.isChildActive())) ? "-selected" : "")} ref={this._viewer} />;
}
@computed get contentScaling() { return this.props.ContentScaling(); }
@computed get standinViews() {
@@ -717,8 +713,8 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
onScroll={this.onScroll} onWheel={this.onZoomWheel} onPointerDown={this.onPointerDown} onClick={this.onClick}
style={{
overflowX: this._zoomed !== 1 ? "scroll" : undefined,
- width: !this.props.Document._fitWidth ? NumCast(this.props.Document._nativeWidth) : `${100 / this.contentScaling}%`,
- height: !this.props.Document._fitWidth ? NumCast(this.props.Document._nativeHeight) : `${100 / this.contentScaling}%`,
+ width: !this.props.Document._fitWidth && (window.screen.width > 600) ? NumCast(this.props.Document._nativeWidth) : `${100 / this.contentScaling}%`,
+ height: !this.props.Document._fitWidth && (window.screen.width > 600) ? NumCast(this.props.Document._nativeHeight) : `${100 / this.contentScaling}%`,
transform: `scale(${this.props.ContentScaling()})`
}} >
{this.pdfViewerDiv}
@@ -750,4 +746,4 @@ class PdfViewerMarquee extends React.Component<PdfViewerMarqueeProps> {
}}>
</div>;
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/presentationview/PresElementBox.scss b/src/client/views/presentationview/PresElementBox.scss
index 190b2c674..6d37ede8a 100644
--- a/src/client/views/presentationview/PresElementBox.scss
+++ b/src/client/views/presentationview/PresElementBox.scss
@@ -143,4 +143,4 @@
display: flex;
justify-content: center;
align-items: center;
-} \ No newline at end of file
+}
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index 561ba7062..bca63e94d 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -49,7 +49,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
componentDidMount() {
this._heightDisposer = reaction(() => [this.rootDoc.presExpandInlineButton, this.collapsedHeight],
- params => this.layoutDoc._height = NumCast(params[1]) + (Number(params[0]) ? 100 : 0), { fireImmediately: true });
+ params => this.layoutDoc._height = NumCast(params[1]) + (Number(params[0]) ? 200 : 0), { fireImmediately: true });
}
componentWillUnmount() {
this._heightDisposer?.();
diff --git a/src/fields/DateField.ts b/src/fields/DateField.ts
index a925148c2..bee62663e 100644
--- a/src/fields/DateField.ts
+++ b/src/fields/DateField.ts
@@ -29,6 +29,10 @@ export class DateField extends ObjectField {
[ToString]() {
return this.date.toISOString();
}
+
+ getDate() {
+ return this.date;
+ }
}
Scripting.addGlobal(function d(...dateArgs: any[]) {
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index e4d11dd4d..7aa1d528d 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -832,12 +832,14 @@ export namespace Doc {
})(doc);
}
export function BrushDoc(doc: Doc) {
+
if (!doc || doc[AclSym] === AclPrivate || Doc.GetProto(doc)[AclSym] === AclPrivate) return doc;
brushManager.BrushedDoc.set(doc, true);
brushManager.BrushedDoc.set(Doc.GetProto(doc), true);
return doc;
}
export function UnBrushDoc(doc: Doc) {
+
if (!doc || doc[AclSym] === AclPrivate || Doc.GetProto(doc)[AclSym] === AclPrivate) return doc;
brushManager.BrushedDoc.delete(doc);
brushManager.BrushedDoc.delete(Doc.GetProto(doc));
@@ -965,7 +967,7 @@ export namespace Doc {
container._docFilters = new List<string>(docFilters);
}
}
- })
+ });
}
export function readDocRangeFilter(doc: Doc, key: string) {
const docRangeFilters = Cast(doc._docRangeFilters, listSpec("string"), []);
diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts
index 7c7bf3e12..a590c88c4 100644
--- a/src/fields/RichTextUtils.ts
+++ b/src/fields/RichTextUtils.ts
@@ -392,7 +392,7 @@ export namespace RichTextUtils {
const { attrs } = mark;
switch (converted) {
case "link":
- let url = attrs.allHrefs.length ? attrs.allHrefs[0].href : "";
+ let url = attrs.allLinks.length ? attrs.allLinks[0].href : "";
const delimiter = "/doc/";
const alreadyShared = "?sharing=true";
if (new RegExp(window.location.origin + delimiter).test(url) && !url.endsWith(alreadyShared)) {
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 2869ebed1..2dc21c987 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -78,7 +78,7 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
} else {
target.__fields[prop] = value;
}
- if (typeof value === "object" && !(value instanceof ObjectField)) debugger;
+ //if (typeof value === "object" && !(value instanceof ObjectField)) debugger;
if (writeToServer) {
if (value === undefined) target[Update]({ '$unset': { ["fields." + prop]: "" } });
else target[Update]({ '$set': { ["fields." + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : (value === undefined ? null : value) } });
diff --git a/src/mobile/AudioUpload.scss b/src/mobile/AudioUpload.scss
new file mode 100644
index 000000000..6e64d9e2e
--- /dev/null
+++ b/src/mobile/AudioUpload.scss
@@ -0,0 +1,61 @@
+@import "../client/views/globalCssVariables.scss";
+
+.audioUpload_cont {
+ display: flex;
+ justify-content: center;
+ flex-direction: column;
+ align-items: center;
+ margin-top: 10px;
+ height: 400px;
+ width: 600px;
+}
+
+.upload_label {
+ position: relative;
+ font-weight: 700;
+ color: black;
+ background-color: rgba(0, 0, 0, 0);
+ border: solid 3px black;
+ margin: 10px;
+ font-size: 30;
+ height: 70px;
+ width: 60%;
+ display: inline-flex;
+ font-family: sans-serif;
+ text-transform: uppercase;
+ justify-content: center;
+ flex-direction: column;
+ border-radius: 10px;
+}
+
+.restart_label {
+ position: relative;
+ font-weight: 700;
+ color: black;
+ background-color: rgba(0, 0, 0, 0);
+ border: solid 3px black;
+ margin: 10px;
+ font-size: 30;
+ height: 70px;
+ width: 60%;
+ display: inline-flex;
+ font-family: sans-serif;
+ text-transform: uppercase;
+ justify-content: center;
+ flex-direction: column;
+ border-radius: 10px;
+}
+
+.audio-upload {
+ top: 100%;
+ opacity: 0;
+}
+
+.audio-upload.active {
+ top: 0;
+ position: absolute;
+ z-index: 999;
+ height: 100vh;
+ width: 100vw;
+ opacity: 1;
+} \ No newline at end of file
diff --git a/src/mobile/AudioUpload.tsx b/src/mobile/AudioUpload.tsx
new file mode 100644
index 000000000..590bf8f9d
--- /dev/null
+++ b/src/mobile/AudioUpload.tsx
@@ -0,0 +1,152 @@
+import { Docs } from '../client/documents/Documents';
+import "./ImageUpload.scss";
+import React = require('react');
+import { observer } from 'mobx-react';
+import { observable, action, computed } from 'mobx';
+import { Utils, emptyPath, returnFalse, emptyFunction, returnOne, returnZero, returnTrue, returnEmptyFilter } from '../Utils';
+import { Doc, Opt } from '../fields/Doc';
+import { Cast, FieldValue } from '../fields/Types';
+import { listSpec } from '../fields/Schema';
+import MainViewModal from '../client/views/MainViewModal';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { nullAudio } from '../fields/URLField';
+import { Transform } from '../client/util/Transform';
+import { DocumentView } from '../client/views/nodes/DocumentView';
+import { MobileInterface } from './MobileInterface';
+import { DictationOverlay } from '../client/views/DictationOverlay';
+import RichTextMenu from '../client/views/nodes/formattedText/RichTextMenu';
+import { ContextMenu } from '../client/views/ContextMenu';
+
+@observer
+export class AudioUpload extends React.Component {
+ @observable public _audioCol: Doc = FieldValue(Cast(Docs.Create.FreeformDocument([Cast(Docs.Create.AudioDocument(nullAudio, { title: "mobile audio", _width: 500, _height: 100 }), Doc) as Doc], { title: "mobile audio", _width: 300, _height: 300, _fitToBox: true, boxShadow: "0 0" }), Doc)) as Doc;
+
+ /**
+ * Handles the onclick functionality for the 'Restart' button
+ * Resets the document to its default view
+ */
+ @action
+ clearUpload = () => {
+ for (let i = 1; i < 8; i++) {
+ this.setOpacity(i, "0.2");
+ }
+ this._audioCol = FieldValue(Cast(
+ Docs.Create.FreeformDocument(
+ [Cast(Docs.Create.AudioDocument(nullAudio, {
+ title: "mobile audio",
+ _width: 500,
+ _height: 100
+ }), Doc) as Doc], { title: "mobile audio", _width: 300, _height: 300, _fitToBox: true, boxShadow: "0 0" }), Doc)) as Doc;
+ }
+
+ /**
+ * Handles the onClick of the 'Close' button
+ * Reset upload interface and toggle audio
+ */
+ closeUpload = () => {
+ this.clearUpload();
+ MobileInterface.Instance.toggleAudio();
+ }
+
+ /**
+ * Handles the on click of the 'Upload' button.
+ * Pushing the audio doc onto Dash Web through the right side bar
+ */
+ uploadAudio = () => {
+ const audioRightSidebar = Cast(Doc.UserDoc().rightSidebarCollection, Doc) as Doc;
+ const audioDoc = this._audioCol;
+ const data = Cast(audioRightSidebar.data, listSpec(Doc));
+ for (let i = 1; i < 8; i++) {
+ setTimeout(() => this.setOpacity(i, "1"), i * 200);
+ }
+ if (data) {
+ data.push(audioDoc);
+ }
+ // Resets uploader after 3 seconds
+ setTimeout(this.clearUpload, 3000);
+ }
+
+ // Returns the upload audio menu
+ private get uploadInterface() {
+ return (
+ <>
+ <ContextMenu />
+ <DictationOverlay />
+ <div style={{ display: "none" }}><RichTextMenu key="rich" /></div>
+ <div className="closeUpload" onClick={() => this.closeUpload()}>
+ <FontAwesomeIcon icon="window-close" size={"lg"} />
+ </div>
+ <FontAwesomeIcon icon="microphone" size="lg" style={{ fontSize: "130" }} />
+ <div className="audioUpload_cont">
+ <DocumentView
+ Document={this._audioCol}
+ DataDoc={undefined}
+ LibraryPath={emptyPath}
+ addDocument={undefined}
+ addDocTab={returnFalse}
+ pinToPres={emptyFunction}
+ rootSelected={returnTrue}
+ removeDocument={undefined}
+ docFilters={returnEmptyFilter}
+ onClick={undefined}
+ ScreenToLocalTransform={Transform.Identity}
+ ContentScaling={returnOne}
+ PanelWidth={() => 600}
+ PanelHeight={() => 400}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
+ renderDepth={0}
+ focus={emptyFunction}
+ backgroundColor={() => "rgba(0,0,0,0)"}
+ parentActive={returnTrue}
+ whenActiveChanged={emptyFunction}
+ bringToFront={emptyFunction}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ />
+ </div>
+ <div className="restart_label" onClick={this.clearUpload}>
+ Restart
+ </div>
+ <div className="upload_label" onClick={this.uploadAudio}>
+ Upload
+ </div>
+ <div className="loadingImage">
+ <div className="loadingSlab" id="slab01" />
+ <div className="loadingSlab" id="slab02" />
+ <div className="loadingSlab" id="slab03" />
+ <div className="loadingSlab" id="slab04" />
+ <div className="loadingSlab" id="slab05" />
+ <div className="loadingSlab" id="slab06" />
+ <div className="loadingSlab" id="slab07" />
+ </div>
+ </>
+ );
+ }
+
+ // Handles the setting of the loading bar
+ setOpacity = (index: number, opacity: string) => {
+ const slab = document.getElementById("slab0" + index);
+ if (slab) {
+ slab.style.opacity = opacity;
+ }
+ }
+
+ @observable private dialogueBoxOpacity = 1;
+ @observable private overlayOpacity = 0.4;
+
+ render() {
+ return (
+ <MainViewModal
+ contents={this.uploadInterface}
+ isDisplayed={true}
+ interactive={true}
+ dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity}
+ overlayDisplayedOpacity={this.overlayOpacity}
+ />
+ );
+ }
+
+}
+
+
diff --git a/src/mobile/ImageUpload.scss b/src/mobile/ImageUpload.scss
index eea69b81c..890258918 100644
--- a/src/mobile/ImageUpload.scss
+++ b/src/mobile/ImageUpload.scss
@@ -5,8 +5,32 @@
justify-content: center;
flex-direction: column;
align-items: center;
- width: 100vw;
- height: 100vh;
+ max-width: 400px;
+ min-width: 400px;
+
+ .upload_label {
+ font-weight: 700;
+ color: black;
+ background-color: rgba(0, 0, 0, 0);
+ border: solid 3px black;
+ margin: 10px;
+ font-size: 30;
+ height: 70px;
+ width: 80%;
+ display: flex;
+ font-family: sans-serif;
+ text-transform: uppercase;
+ justify-content: center;
+ flex-direction: column;
+ border-radius: 10px;
+ }
+
+ .file {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ direction: ltr;
+ }
.button_file {
text-align: center;
@@ -17,18 +41,98 @@
font-size: 3em;
}
- .input_file {
- display: none;
+ .inputfile {
+ width: 0.1px;
+ height: 0.1px;
+ opacity: 0;
+ overflow: hidden;
+ position: absolute;
+ z-index: -1;
}
- .upload_label,
- .upload_button {
- background: $dark-color;
- font-size: 500%;
- font-family: $sans-serif;
- text-align: center;
- padding: 5vh;
- margin-bottom: 20px;
- color: white;
+ .inputfile+label {
+ font-weight: 700;
+ color: black;
+ background-color: rgba(0, 0, 0, 0);
+ border: solid 3px black;
+ margin: 10px;
+ font-size: 30;
+ height: 70px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ margin-top: 30px;
+ width: 80%;
+ display: flex;
+ font-family: sans-serif;
+ text-transform: uppercase;
+ justify-content: center;
+ flex-direction: column;
+ border-radius: 10px;
+ }
+
+ .inputfile.active+label {
+ font-style: italic;
+ color: black;
+ background-color: lightgreen;
+ border: solid 3px darkgreen;
+ }
+
+ .status {
+ font-size: 2em;
}
+
+}
+
+.image-upload {
+ top: 100%;
+ opacity: 0;
+}
+
+.image-upload.active {
+ top: 0;
+ position: absolute;
+ z-index: 999;
+ height: 100vh;
+ width: 100vw;
+ opacity: 1;
+}
+
+.uploadContainer {
+ top: 40;
+ position: absolute;
+ z-index: 1000;
+ height: 20vh;
+ width: 80vw;
+ opacity: 1;
+}
+
+.closeUpload {
+ position: absolute;
+ border-radius: 10px;
+ top: 3;
+ color: black;
+ font-size: 30;
+ right: 3;
+ z-index: 1002;
+ padding: 0px 3px;
+ background: rgba(0, 0, 0, 0);
+ transition: 0.5s ease all;
+ border: 0px solid;
+}
+
+.loadingImage {
+ display: inline-flex;
+ width: max-content;
+}
+
+.loadingSlab {
+ position: relative;
+ width: 30px;
+ height: 30px;
+ margin: 10;
+ border-radius: 20px;
+ opacity: 0.2;
+ background-color: black;
+ transition: all 2s, opacity 1.5s;
} \ No newline at end of file
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index b15042f9f..5ea626d52 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -1,128 +1,179 @@
-import * as ReactDOM from 'react-dom';
import * as rp from 'request-promise';
import { Docs } from '../client/documents/Documents';
import "./ImageUpload.scss";
import React = require('react');
import { DocServer } from '../client/DocServer';
-import { Opt, Doc } from '../fields/Doc';
+import { observer } from 'mobx-react';
+import { observable, action } from 'mobx';
+import { Utils } from '../Utils';
+import { Networking } from '../client/Network';
+import { Doc, Opt } from '../fields/Doc';
import { Cast } from '../fields/Types';
import { listSpec } from '../fields/Schema';
import { List } from '../fields/List';
-import { observer } from 'mobx-react';
-import { observable } from 'mobx';
-import { Utils } from '../Utils';
-import MobileInterface from './MobileInterface';
-import { CurrentUserUtils } from '../client/util/CurrentUserUtils';
-import { resolvedPorts } from '../client/views/Main';
+import MainViewModal from '../client/views/MainViewModal';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { MobileInterface } from './MobileInterface';
+
+export interface ImageUploadProps {
+ Document: Doc; // Target document for upload (upload location)
+}
-// const onPointerDown = (e: React.TouchEvent) => {
-// let imgInput = document.getElementById("input_image_file");
-// if (imgInput) {
-// imgInput.click();
-// }
-// }
const inputRef = React.createRef<HTMLInputElement>();
@observer
-class Uploader extends React.Component {
+export class Uploader extends React.Component<ImageUploadProps> {
@observable error: string = "";
- @observable status: string = "";
+ @observable nm: string = "Choose files"; // Text of 'Choose Files' button
+ @observable process: string = ""; // Current status of upload
onClick = async () => {
- console.log("uploader click");
try {
- this.status = "initializing protos";
+ const col = this.props.Document;
await Docs.Prototypes.initialize();
const imgPrev = document.getElementById("img_preview");
+ this.setOpacity(1, "1"); // Slab 1
if (imgPrev) {
const files: FileList | null = inputRef.current!.files;
+ this.setOpacity(2, "1"); // Slab 2
if (files && files.length !== 0) {
- console.log(files[0]);
- const name = files[0].name;
- const formData = new FormData();
- formData.append("file", files[0]);
-
- const upload = window.location.origin + "/uploadFormData";
- this.status = "uploading image";
- console.log("uploading image", formData);
- const res = await fetch(upload, {
- method: 'POST',
- body: formData
- });
- this.status = "upload image, getting json";
- const json = await res.json();
- json.map(async (file: any) => {
- const path = window.location.origin + file;
- const doc = Docs.Create.ImageDocument(path, { _nativeWidth: 200, _width: 200, title: name });
-
- this.status = "getting user document";
-
- const res = await rp.get(Utils.prepend("/getUserDocumentId"));
- if (!res) {
- throw new Error("No user id returned");
- }
- const field = await DocServer.GetRefField(res);
- let pending: Opt<Doc>;
- if (field instanceof Doc) {
- pending = await Cast(field.rightSidebarCollection, Doc);
- }
- if (pending) {
- this.status = "has pending docs";
- const data = await Cast(pending.data, listSpec(Doc));
- if (data) {
- data.push(doc);
+ this.process = "Uploading Files";
+ for (let index = 0; index < files.length; ++index) {
+ const file = files[index];
+ const res = await Networking.UploadFilesToServer(file);
+ this.setOpacity(3, "1"); // Slab 3
+ // For each item that the user has selected
+ res.map(async ({ result }) => {
+ const name = file.name;
+ if (result instanceof Error) {
+ return;
+ }
+ const path = Utils.prepend(result.accessPaths.agnostic.client);
+ let doc = null;
+ // Case 1: File is a video
+ if (file.type === "video/mp4") {
+ doc = Docs.Create.VideoDocument(path, { _nativeWidth: 400, _width: 400, title: name });
+ // Case 2: File is a PDF document
+ } else if (file.type === "application/pdf") {
+ doc = Docs.Create.PdfDocument(path, { _nativeWidth: 400, _width: 400, title: name });
+ // Case 3: File is another document type (most likely Image)
} else {
- pending.data = new List([doc]);
+ doc = Docs.Create.ImageDocument(path, { _nativeWidth: 400, _width: 400, title: name });
}
- this.status = "finished";
- }
- });
-
- // console.log(window.location.origin + file[0])
-
- //imgPrev.setAttribute("src", window.location.origin + files[0].name)
+ this.setOpacity(4, "1"); // Slab 4
+ const res = await rp.get(Utils.prepend("/getUserDocumentId"));
+ if (!res) {
+ throw new Error("No user id returned");
+ }
+ const field = await DocServer.GetRefField(res);
+ let pending: Opt<Doc>;
+ if (field instanceof Doc) {
+ pending = col;
+ }
+ if (pending) {
+ const data = await Cast(pending.data, listSpec(Doc));
+ if (data) data.push(doc);
+ else pending.data = new List([doc]);
+ this.setOpacity(5, "1"); // Slab 5
+ this.process = "File " + (index + 1).toString() + " Uploaded";
+ this.setOpacity(6, "1"); // Slab 6
+ }
+ if ((index + 1) === files.length) {
+ this.process = "Uploads Completed";
+ this.setOpacity(7, "1"); // Slab 7
+ }
+ });
+ }
+ // Case in which the user pressed upload and no files were selected
+ } else {
+ this.process = "No file selected";
}
+ // Three seconds after upload the menu will reset
+ setTimeout(this.clearUpload, 3000);
}
} catch (error) {
this.error = JSON.stringify(error);
}
}
- render() {
+ // Updates label after a files is selected (so user knows a file is uploaded)
+ inputLabel = async () => {
+ const files: FileList | null = inputRef.current!.files;
+ await files;
+ if (files && files.length === 1) {
+ this.nm = files[0].name;
+ } else if (files && files.length > 1) {
+ this.nm = files.length.toString() + " files selected";
+ }
+ }
+
+ // Loops through load icons, and resets buttons
+ @action
+ clearUpload = () => {
+ for (let i = 1; i < 8; i++) {
+ this.setOpacity(i, "0.2");
+ }
+ this.nm = "Choose files";
+
+ if (inputRef.current) {
+ inputRef.current.value = "";
+ }
+ this.process = "";
+ }
+
+ // Clears the upload and closes the upload menu
+ closeUpload = () => {
+ this.clearUpload();
+ MobileInterface.Instance.toggleUpload();
+ }
+
+ // Handles the setting of the loading bar
+ setOpacity = (index: number, opacity: string) => {
+ const slab = document.getElementById("slab" + index);
+ if (slab) slab.style.opacity = opacity;
+ }
+
+ // Returns the upload interface for mobile
+ private get uploadInterface() {
return (
<div className="imgupload_cont">
- <label htmlFor="input_image_file" className="upload_label">Choose an Image</label>
- <input type="file" accept="image/*" className="input_file" id="input_image_file" ref={inputRef}></input>
- <button onClick={this.onClick} className="upload_button">Upload</button>
+ <div className="closeUpload" onClick={() => this.closeUpload()}>
+ <FontAwesomeIcon icon="window-close" size={"lg"} />
+ </div>
+ <FontAwesomeIcon icon="upload" size="lg" style={{ fontSize: "130" }} />
+ <input type="file" accept="application/pdf, video/*,image/*" className={`inputFile ${this.nm !== "Choose files" ? "active" : ""}`} id="input_image_file" ref={inputRef} onChange={this.inputLabel} multiple></input>
+ <label className="file" id="label" htmlFor="input_image_file">{this.nm}</label>
+ <div className="upload_label" onClick={this.onClick}>
+ Upload
+ </div>
<img id="img_preview" src=""></img>
- <p>{this.status}</p>
- <p>{this.error}</p>
+ <div className="loadingImage">
+ <div className="loadingSlab" id="slab1" />
+ <div className="loadingSlab" id="slab2" />
+ <div className="loadingSlab" id="slab3" />
+ <div className="loadingSlab" id="slab4" />
+ <div className="loadingSlab" id="slab5" />
+ <div className="loadingSlab" id="slab6" />
+ <div className="loadingSlab" id="slab7" />
+ </div>
+ <p className="status">{this.process}</p>
</div>
);
}
-}
+ @observable private dialogueBoxOpacity = 1;
+ @observable private overlayOpacity = 0.4;
-
-// DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts.socket, "image upload");
-(async () => {
- const info = await CurrentUserUtils.loadCurrentUser();
- DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts.socket, info.email + "mobile");
- await Docs.Prototypes.initialize();
- if (info.id !== "__guest__") {
- // a guest will not have an id registered
- await CurrentUserUtils.loadUserDocument(info);
+ render() {
+ return (
+ <MainViewModal
+ contents={this.uploadInterface}
+ isDisplayed={true}
+ interactive={true}
+ dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity}
+ overlayDisplayedOpacity={this.overlayOpacity}
+ />
+ );
}
- document.getElementById('root')!.addEventListener('wheel', event => {
- if (event.ctrlKey) {
- event.preventDefault();
- }
- }, true);
- ReactDOM.render((
- // <Uploader />
- <MobileInterface />
- ),
- document.getElementById('root')
- );
+
}
-)(); \ No newline at end of file
diff --git a/src/mobile/MobileInkOverlay.tsx b/src/mobile/MobileInkOverlay.tsx
index 59c73ed27..d668d134e 100644
--- a/src/mobile/MobileInkOverlay.tsx
+++ b/src/mobile/MobileInkOverlay.tsx
@@ -4,11 +4,9 @@ import { MobileInkOverlayContent, GestureContent, UpdateMobileInkOverlayPosition
import { observable, action } from "mobx";
import { GestureUtils } from "../pen-gestures/GestureUtils";
import "./MobileInkOverlay.scss";
-import { StrCast, Cast } from '../fields/Types';
import { DragManager } from "../client/util/DragManager";
import { DocServer } from '../client/DocServer';
-import { Doc, DocListCastAsync } from '../fields/Doc';
-import { listSpec } from '../fields/Schema';
+import { Doc } from '../fields/Doc';
@observer
diff --git a/src/mobile/MobileInterface.scss b/src/mobile/MobileInterface.scss
index 4d86e208f..4b32c3da0 100644
--- a/src/mobile/MobileInterface.scss
+++ b/src/mobile/MobileInterface.scss
@@ -1,19 +1,445 @@
-.mobileInterface-inkInterfaceButtons {
- position: absolute;
+$navbar-height: 120px;
+$pathbar-height: 50px;
+
+@media only screen and (max-device-width: 480px) {
+ * {
+ margin: 0px;
+ padding: 0px;
+ box-sizing: border-box;
+ font-family: sans-serif;
+ }
+}
+
+body {
+ overflow: hidden;
+}
+
+.mobileInterface-container {
+ height: 100%;
+ position: relative;
+ touch-action: none;
+ width: 100%;
+
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+}
+
+// Topbar of Dash Mobile
+.navbar {
+ position: fixed;
top: 0px;
+ left: 0px;
+ width: 100vw;
+ height: $navbar-height;
+ background-color: whitesmoke;
+ z-index: 150;
+
+ .cover {
+ position: absolute;
+ right: 0px;
+ top: 0px;
+ height: 120px;
+ width: 120px;
+ background-color: whitesmoke;
+ z-index: 200;
+ }
+
+ .toggle-btn {
+ position: absolute;
+ right: 20px;
+ top: 30px;
+ height: 70px;
+ width: 70px;
+ transition: all 400ms ease-in-out 200ms;
+ z-index: 180;
+ }
+
+ .background {
+ position: absolute;
+ right: 0px;
+ top: 0px;
+ height: 120px;
+ width: 120px;
+ //border: 1px solid black;
+ }
+
+ .background.active {
+ background-color: lightgrey;
+ }
+
+ .toggle-btn-home {
+ right: -200px;
+ }
+
+ .header {
+ position: absolute;
+ top: 50%;
+ top: calc(9px + 50%);
+ right: 50%;
+ transform: translate(50%, -50%);
+ font-size: 40;
+ font-weight: 700;
+ text-align: center;
+ user-select: none;
+ text-transform: uppercase;
+ font-family: Arial, Helvetica, sans-serif;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ direction: ltr;
+ width: 600px;
+ }
+
+ .toggle-btn span {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: 70%;
+ height: 4px;
+ background: black;
+ transition: all 200ms ease;
+ z-index: 180;
+ }
+
+ .toggle-btn span:nth-child(1) {
+ transition: top 200ms ease-in-out;
+ top: 30%;
+ }
+
+ .toggle-btn span:nth-child(3) {
+ transition: top 200ms ease-in-out;
+ top: 70%;
+ }
+
+ .toggle-btn.active {
+ transition: transform 200ms ease-in-out 200ms;
+ transform: rotate(135deg);
+ }
+
+ .toggle-btn.active span:nth-child(1) {
+ top: 50%;
+ }
+
+ .toggle-btn.active span:nth-child(2) {
+ transform: translate(-50%, -50%) rotate(90deg);
+ }
+
+ .toggle-btn.active span:nth-child(3) {
+ top: 50%;
+ }
+}
+
+.sidebar {
+ position: fixed;
+ top: 120px;
+ opacity: 0;
+ right: -100%;
+ width: 80%;
+ height: calc(80% - (120px));
+ z-index: 101;
+ background-color: whitesmoke;
+ transition: all 400ms ease 50ms;
+ padding: 20px;
+ box-shadow: 0 0 5px 5px grey;
+
+ .item {
+ width: 100%;
+ padding: 13px 12px;
+ border-bottom: 1px solid rgba(200, 200, 200, 0.7);
+ font-family: Arial, Helvetica, sans-serif;
+ font-style: normal;
+ font-weight: normal;
+ user-select: none;
+ display: inline-flex;
+ font-size: 35px;
+ text-transform: uppercase;
+ color: black;
+ }
+
+ .ink:focus {
+ outline: 1px solid blue;
+ }
+
+ .sidebarButtons {
+ top: 80px;
+ position: relative;
+ }
+}
+
+
+
+
+
+
+.blanket {
+ position: fixed;
+ top: 120px;
+ opacity: 0.5;
+ right: -100%;
+ width: 100%;
+ height: calc(100% - (120px));
+ z-index: 101;
+ background-color: grey;
+ padding: 20px;
+}
+
+.blanket.active {
+ position: absolute;
+ right: 0%;
+ z-index: 100;
+}
+
+.home {
+ position: absolute;
+ top: 30px;
+ left: 30px;
+ font-size: 60;
+ user-select: none;
+ text-transform: uppercase;
+ font-family: Arial, Helvetica, sans-serif;
+ z-index: 200;
+}
+
+.item-type {
+ display: inline;
+ text-transform: lowercase;
+ margin-left: 20px;
+ font-size: 35px;
+ font-style: italic;
+ color: rgb(28, 28, 28);
+}
+
+.item-title {
+ max-width: 70%;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.right {
+ margin-left: 20px;
+ z-index: 200;
+}
+
+.open {
+ right: 20px;
+ font-size: 35;
+ position: absolute;
+}
+
+.left {
+ width: 100%;
+ height: 100%;
+}
+
+
+
+.sidebar.active {
+ position: absolute;
+ right: 0%;
+ opacity: 1;
+ z-index: 101;
+}
+
+.back {
+ position: absolute;
+ left: 42px;
+ top: 0;
+ background: #1a1a1a;
+ width: 50px;
+ height: 100%;
display: flex;
- justify-content: space-between;
+ justify-content: center;
+ text-align: center;
+ flex-direction: column;
+ align-items: center;
+ border-radius: 10px;
+ font-size: 25px;
+ user-select: none;
+ z-index: 100;
+}
+
+.pathbar {
+ position: fixed;
+ top: 118px;
+ left: 0px;
+ background: #1a1a1a;
+ z-index: 120;
+ border-radius: 0px;
width: 100%;
- z-index: 9999;
- height: 50px;
+ height: 80px;
+ overflow: hidden;
+
+ .pathname {
+ position: relative;
+ font-size: 25;
+ top: 50%;
+ width: 86%;
+ left: 12%;
+ color: whitesmoke;
+ transform: translate(0%, -50%);
+ z-index: 20;
+ font-family: sans-serif;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ direction: rtl;
+ text-align: left;
+ text-transform: uppercase;
+ }
+
+ .scrollmenu {
+ overflow: auto;
+ width: 100%;
+ height: 100%;
+ white-space: nowrap;
+ display: inline-flex;
+ }
- .mobileInterface-button {
+ .hidePath {
+ position: absolute;
height: 100%;
+ width: 200px;
+ left: 0px;
+ top: 0px;
+ background-image: linear-gradient(to right, #1a1a1a, rgba(0, 0, 0, 0));
+ text-align: center;
+ user-select: none;
+ z-index: 99;
+ pointer-events: none;
+ }
+
+ .pathbarItem {
+ position: relative;
+ display: flex;
+ align-items: center;
+ color: whitesmoke;
+ text-align: center;
+ justify-content: center;
+ user-select: none;
+ transform: translate(100px, 0px);
+ font-size: 30px;
+ padding: 10px;
+ text-transform: uppercase;
+
+ .pathbarText {
+ font-family: sans-serif;
+ text-align: center;
+ height: 50px;
+ padding: 10px;
+ font-size: 30px;
+ border-radius: 10px;
+ text-transform: uppercase;
+ margin-left: 20px;
+ position: relative;
+ }
+
+ .pathIcon {
+ transform: translate(0px, 0px);
+ position: relative;
+ }
}
}
-.mobileInterface-container {
- height: 100%;
+
+/**
+* docButton appears at the bottom of mobile document
+* Buttons include: pin to presentation, download, upload, reload
+*/
+.docButton {
position: relative;
- touch-action: none;
+ width: 100px;
+ display: flex;
+ height: 100px;
+ font-size: 70px;
+ text-align: center;
+ border: 3px solid black;
+ margin: 20px;
+ z-index: 100;
+ border-radius: 100%;
+ justify-content: center;
+ flex-direction: column;
+ align-items: center;
+}
+
+.docButtonContainer {
+ top: 80%;
+ position: absolute;
+ display: flex;
+ transform: translate(-50%, 0);
+ left: 50%;
+ z-index: 100;
+}
+
+.toolbar {
+ left: 50%;
+ transform: translate(-50%);
+ position: absolute;
+ height: max-content;
+ top: 0px;
+ border-radius: 20px;
+ background-color: lightgrey;
+ opacity: 0;
+ transition: all 400ms ease 50ms;
+}
+
+.toolbar.active {
+ display: inline-block;
+ width: 300px;
+ padding: 5px;
+ opacity: 1;
+ height: max-content;
+ top: -450px;
+}
+
+.colorSelector {
+ position: absolute;
+ top: 550px;
+ left: 280px;
+ transform: translate(-50%, 0);
+ z-index: 100;
+ display: inline-flex;
+ width: max-content;
+ height: max-content;
+ pointer-events: all;
+ font-size: 80px;
+ user-select: none;
+}
+
+// Menu buttons for toggling between list and icon view
+.homeSwitch {
+ position: fixed;
+ top: 212;
+ right: 36px;
+ display: inline-flex;
+ width: max-content;
+ z-index: 99;
+ height: 70px;
+
+ .list {
+ width: 70px;
+ height: 70px;
+ margin: 5;
+ padding: 10;
+ align-items: center;
+ text-align: center;
+ font-size: 50;
+ border-style: solid;
+ border-width: 3;
+ border-color: black;
+ background: whitesmoke;
+ align-self: center;
+ border-radius: 10px;
+ }
+
+ .list.active {
+ color: darkred;
+ border-color: darkred;
+ }
} \ No newline at end of file
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index a50ec103e..fafa94a11 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -1,47 +1,69 @@
-import React = require('react');
+import * as React from "react";
import { library } from '@fortawesome/fontawesome-svg-core';
-import { faEraser, faHighlighter, faLongArrowAltLeft, faMousePointer, faPenNib } from '@fortawesome/free-solid-svg-icons';
+import {
+ faTasks, faReply, faQuoteLeft, faHandPointLeft, faFolderOpen, faAngleDoubleLeft, faExternalLinkSquareAlt, faMobile, faThLarge, faWindowClose, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
+ faTerminal, faToggleOn, faFile as fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard,
+ faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
+ faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
+ faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote,
+ faThumbtack, faTree, faTv, faBook, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faHome, faLongArrowAltLeft, faBars, faTh, faChevronLeft,
+ faAlignRight, faAlignLeft
+} from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable } from 'mobx';
+import { action, computed, observable, reaction, trace, runInAction } from 'mobx';
import { observer } from 'mobx-react';
-import { DocServer } from '../client/DocServer';
-import { Docs } from '../client/documents/Documents';
-import { DocumentManager } from '../client/util/DocumentManager';
-import RichTextMenu from '../client/views/nodes/formattedText/RichTextMenu';
+import { Doc, DocListCast } from '../fields/Doc';
+import { CurrentUserUtils } from '../client/util/CurrentUserUtils';
+import { emptyFunction, emptyPath, returnFalse, returnOne, returnTrue, returnZero, returnEmptyFilter } from '../Utils';
+import { Docs, DocumentOptions } from '../client/documents/Documents';
import { Scripting } from '../client/util/Scripting';
-import { Transform } from '../client/util/Transform';
-import { DocumentDecorations } from '../client/views/DocumentDecorations';
-import GestureOverlay from '../client/views/GestureOverlay';
import { DocumentView } from '../client/views/nodes/DocumentView';
-import { RadialMenu } from '../client/views/nodes/RadialMenu';
-import { PreviewCursor } from '../client/views/PreviewCursor';
-import { Doc, DocListCast, FieldResult } from '../fields/Doc';
-import { Id } from '../fields/FieldSymbols';
+import { Transform } from '../client/util/Transform';
+import "./MobileInterface.scss";
+import "./ImageUpload.scss";
+import "./AudioUpload.scss";
+import SettingsManager from '../client/util/SettingsManager';
+import { Uploader } from "./ImageUpload";
+import { DockedFrameRenderer } from '../client/views/collections/CollectionDockingView';
import { InkTool } from '../fields/InkField';
-import { listSpec } from '../fields/Schema';
+import GestureOverlay from "../client/views/GestureOverlay";
+import { ScriptField } from "../fields/ScriptField";
+import InkOptionsMenu from "../client/views/collections/collectionFreeForm/InkOptionsMenu";
+import { RadialMenu } from "../client/views/nodes/RadialMenu";
+import { UndoManager } from "../client/util/UndoManager";
+import { List } from "../fields/List";
+import { AudioUpload } from "./AudioUpload";
import { Cast, FieldValue } from '../fields/Types';
-import { WebField } from "../fields/URLField";
-import { CurrentUserUtils } from '../client/util/CurrentUserUtils';
-import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero, returnEmptyFilter } from '../Utils';
-import "./MobileInterface.scss";
-import { CollectionView } from '../client/views/collections/CollectionView';
-import { InkingStroke } from '../client/views/InkingStroke';
+import RichTextMenu from "../client/views/nodes/formattedText/RichTextMenu";
+import { AudioBox } from "../client/views/nodes/AudioBox";
+import { CollectionViewType } from "../client/views/collections/CollectionView";
+import { DocumentType } from "../client/documents/DocumentTypes";
-library.add(faLongArrowAltLeft);
+library.add(faTasks, faReply, faQuoteLeft, faHandPointLeft, faFolderOpen, faAngleDoubleLeft, faExternalLinkSquareAlt, faMobile, faThLarge, faWindowClose, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
+ faTerminal, faToggleOn, fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard,
+ faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
+ faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
+ faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote,
+ faThumbtack, faTree, faTv, faUndoAlt, faBook, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faHome, faLongArrowAltLeft, faBars, faTh, faChevronLeft,
+ faAlignLeft, faAlignRight);
@observer
-export default class MobileInterface extends React.Component {
- @observable static Instance: MobileInterface;
- @computed private get userDoc() { return Doc.UserDoc(); }
- @computed private get mainContainer() { return this.userDoc ? FieldValue(Cast(this.userDoc.activeMobile, Doc)) : CurrentUserUtils.GuestMobile; }
- // @observable private currentView: "main" | "ink" | "upload" = "main";
- private mainDoc: any = CurrentUserUtils.setupMobileDoc(this.userDoc);
- @observable private renderView?: () => JSX.Element;
+export class MobileInterface extends React.Component {
+ static Instance: MobileInterface;
+ private _library: Doc = CurrentUserUtils.setupLibrary(Doc.UserDoc()); // to access documents in Dash Web
+ private _mainDoc: any = CurrentUserUtils.setupActiveMobileMenu(Doc.UserDoc());
+ @observable private _sidebarActive: boolean = false; //to toggle sidebar display
+ @observable private _imageUploadActive: boolean = false; //to toggle image upload
+ @observable private _audioUploadActive: boolean = false;
+ @observable private _menuListView: boolean = false; //to switch between menu view (list / icon)
+ @observable private _ink: boolean = false; //toggle whether ink is being dispalyed
+ @observable private _homeMenu: boolean = true; // to determine whether currently at home menu
+ @observable private _child: Doc | null = null; // currently selected document
+ @observable private _activeDoc: Doc = this._mainDoc; // doc updated as the active mobile page is updated (initially home menu)
+ @observable private _homeDoc: Doc = this._mainDoc; // home menu as a document
+ @observable private _parents: Array<Doc> = []; // array of parent docs (for pathbar)
- // private inkDoc?: Doc;
- public drawingInk: boolean = false;
-
- // private uploadDoc?: Doc;
+ @computed private get mainContainer() { return Doc.UserDoc() ? FieldValue(Cast(Doc.UserDoc().activeMobile, Doc)) : CurrentUserUtils.GuestMobile; }
constructor(props: Readonly<{}>) {
super(props);
@@ -50,298 +72,613 @@ export default class MobileInterface extends React.Component {
@action
componentDidMount = () => {
- library.add(...[faPenNib, faHighlighter, faEraser, faMousePointer]);
+ // if the home menu is in list view -> adjust the menu toggle appropriately
+ this._menuListView = this._homeDoc._viewType === "stacking" ? true : false;
+ Doc.SetSelectedTool(InkTool.None); // ink should intially be set to none
+ Doc.UserDoc().activeMobile = this._homeDoc; // active mobile set to home
+ AudioBox.Enabled = true;
- if (this.userDoc && !this.mainContainer) {
- this.userDoc.activeMobile = this.mainDoc;
- }
+ // remove double click to avoid mobile zoom in
+ document.removeEventListener("dblclick", this.onReactDoubleClick);
+ document.addEventListener("dblclick", this.onReactDoubleClick);
}
@action
- switchCurrentView = (doc: (userDoc: Doc) => Doc, renderView?: () => JSX.Element, onSwitch?: () => void) => {
- if (!this.userDoc) return;
+ componentWillUnmount = () => {
+ document.removeEventListener('dblclick', this.onReactDoubleClick);
+ }
+
+ // Prevent zooming in when double tapping the screen
+ onReactDoubleClick = (e: MouseEvent) => {
+ e.stopPropagation();
+ }
- this.userDoc.activeMobile = doc(this.userDoc);
- onSwitch && onSwitch();
+ // Switch the mobile view to the given doc
+ @action
+ switchCurrentView = (doc: Doc, renderView?: () => JSX.Element, onSwitch?: () => void) => {
+ if (!Doc.UserDoc()) return;
+ if (this._activeDoc === this._homeDoc) {
+ this._parents.push(this._activeDoc);
+ this._homeMenu = false;
+ }
+ this._activeDoc = doc;
+ Doc.UserDoc().activeMobile = doc;
+ onSwitch?.();
- this.renderView = renderView;
+ // Ensures that switching to home is not registed
+ UndoManager.undoStack.length = 0;
+ UndoManager.redoStack.length = 0;
}
- onSwitchInking = () => {
- Doc.SetSelectedTool(InkTool.Pen);
- MobileInterface.Instance.drawingInk = true;
+ // For toggling the hamburger menu
+ @action
+ toggleSidebar = () => {
+ this._sidebarActive = !this._sidebarActive;
- DocServer.Mobile.dispatchOverlayTrigger({
- enableOverlay: true,
- width: window.innerWidth,
- height: window.innerHeight
- });
+ if (this._ink) {
+ this.onSwitchInking();
+ }
+ }
+ /**
+ * Method called when 'Library' button is pressed on the home screen
+ */
+ switchToLibrary = async () => {
+ this.switchCurrentView(this._library);
+ runInAction(() => this._homeMenu = false);
+ this.toggleSidebar();
}
- onSwitchUpload = async () => {
- let width = 300;
- let height = 300;
-
- // get width and height of the collection doc
- if (this.mainContainer) {
- const data = Cast(this.mainContainer.data, listSpec(Doc));
- if (data) {
- const collectionDoc = await data[1]; // this should be the collection doc since the positions should be locked
- const docView = DocumentManager.Instance.getDocumentView(collectionDoc);
- if (docView) {
- width = docView.nativeWidth ? docView.nativeWidth : 300;
- height = docView.nativeHeight ? docView.nativeHeight : 300;
- }
- }
+ /**
+ * Back method for navigating through items
+ */
+ @action
+ back = () => {
+ const header = document.getElementById("header") as HTMLElement;
+ const doc = Cast(this._parents.pop(), Doc) as Doc; // Parent document
+ // Case 1: Parent document is 'workspaces'
+ if (doc === Cast(this._library, Doc) as Doc) {
+ this._child = null;
+ this.switchCurrentView(this._library);
+ // Case 2: Parent document is the 'home' menu (root node)
+ } else if (doc === Cast(this._homeDoc, Doc) as Doc) {
+ this._homeMenu = true;
+ this._parents = [];
+ this._child = null;
+ this.switchCurrentView(this._homeDoc);
+ // Case 3: Parent document is any document
+ } else if (doc) {
+ this._child = doc;
+ this.switchCurrentView(doc);
+ this._homeMenu = false;
+ header.textContent = String(doc.title);
}
- DocServer.Mobile.dispatchOverlayTrigger({
- enableOverlay: true,
- width: width,
- height: height,
- text: "Documents uploaded from mobile will show here",
- });
+ this._ink = false; // turns ink off
}
- renderDefaultContent = () => {
- if (this.mainContainer) {
- return <DocumentView
- Document={this.mainContainer}
- DataDoc={undefined}
- LibraryPath={emptyPath}
- addDocument={returnFalse}
- addDocTab={returnFalse}
- pinToPres={emptyFunction}
- rootSelected={returnFalse}
- removeDocument={undefined}
- onClick={undefined}
- ScreenToLocalTransform={Transform.Identity}
- ContentScaling={returnOne}
- NativeHeight={returnZero}
- NativeWidth={returnZero}
- PanelWidth={() => window.screen.width}
- PanelHeight={() => window.screen.height}
- renderDepth={0}
- focus={emptyFunction}
- backgroundColor={returnEmptyString}
- parentActive={returnTrue}
- whenActiveChanged={emptyFunction}
- bringToFront={emptyFunction}
- docFilters={returnEmptyFilter}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined} />;
+ /**
+ * Return 'Home", which implies returning to 'Home' menu buttons
+ */
+ @action
+ returnHome = () => {
+ if (!this._homeMenu || this._sidebarActive) {
+ this._homeMenu = true;
+ this._parents = [];
+ this._child = null;
+ this.switchCurrentView(this._homeDoc);
+ }
+ if (this._sidebarActive) {
+ this.toggleSidebar();
}
- return "hello";
}
- onBack = (e: React.MouseEvent) => {
- this.switchCurrentView((userDoc: Doc) => this.mainDoc);
- Doc.SetSelectedTool(InkTool.None); // TODO: switch to previous tool
+ /**
+ * Return to primary Workspace in library (Workspaces Doc)
+ */
+ @action
+ returnMain = () => {
+ this._parents = [this._homeDoc];
+ this.switchCurrentView(this._library);
+ this._homeMenu = false;
+ this._child = null;
+ }
+
+ /**
+ * Note: window.innerWidth and window.screen.width compute different values.
+ * window.screen.width is the display size, however window.innerWidth is the
+ * display resolution which computes differently.
+ */
+ returnWidth = () => window.innerWidth; //The windows width
+ returnHeight = () => (window.innerHeight - 300); //Calculating the windows height (-300 to account for topbar)
+ whitebackground = () => "white";
+ /**
+ * DocumentView for graphic display of all documents
+ */
+ @computed get displayWorkspaces() {
+ return !this.mainContainer ? (null) :
+ <div style={{ position: "relative", top: '198px', height: `calc(100% - 350px)`, width: "100%", left: "0%" }}>
+ <DocumentView
+ Document={this.mainContainer}
+ DataDoc={undefined}
+ LibraryPath={emptyPath}
+ addDocument={returnFalse}
+ addDocTab={returnFalse}
+ pinToPres={emptyFunction}
+ rootSelected={returnFalse}
+ removeDocument={undefined}
+ onClick={undefined}
+ ScreenToLocalTransform={Transform.Identity}
+ ContentScaling={returnOne}
+ PanelWidth={this.returnWidth}
+ PanelHeight={this.returnHeight}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
+ renderDepth={0}
+ focus={emptyFunction}
+ backgroundColor={this.whitebackground}
+ parentActive={returnTrue}
+ whenActiveChanged={emptyFunction}
+ bringToFront={emptyFunction}
+ docFilters={returnEmptyFilter}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ />
+ </div>;
+ }
- DocServer.Mobile.dispatchOverlayTrigger({
- enableOverlay: false,
- width: window.innerWidth,
- height: window.innerHeight
+ /**
+ * Handles the click functionality in the library panel.
+ * Navigates to the given doc and updates the sidebar.
+ * @param doc: doc for which the method is called
+ */
+ handleClick = async (doc: Doc) => {
+ runInAction(() => {
+ if (doc.type !== "collection" && this._sidebarActive) {
+ this._parents.push(this._activeDoc);
+ this.switchCurrentView(doc);
+ this._homeMenu = false;
+ this.toggleSidebar();
+ }
+ else {
+ this._parents.push(this._activeDoc);
+ this.switchCurrentView(doc);
+ this._homeMenu = false;
+ this._child = doc;
+ }
});
+ }
- // this.inkDoc = undefined;
- this.drawingInk = false;
+ /**
+ * Called when an item in the library is clicked and should
+ * be opened (open icon on RHS of all menu items)
+ * @param doc doc to be opened
+ */
+ @action
+ openFromSidebar = (doc: Doc) => {
+ this._parents.push(this._activeDoc);
+ this.switchCurrentView(doc);
+ this._homeMenu = false;
+ this._child = doc;
+ this.toggleSidebar();
}
- shiftLeft = (e: React.MouseEvent) => {
- DocServer.Mobile.dispatchOverlayPositionUpdate({
- dx: -10
- });
- e.preventDefault();
- e.stopPropagation();
+ // Renders the graphical pathbar
+ renderPathbar = () => {
+ const docPath = [...this._parents, this._activeDoc];
+ const items = docPath.map((doc: Doc, index: any) =>
+ <div className="pathbarItem" key={index}>
+ {index === 0 ? (null) : <FontAwesomeIcon key="icon" className="pathIcon" icon="angle-right" size="lg" />}
+ <div className="pathbarText"
+ style={{ backgroundColor: this._homeMenu || doc === this._activeDoc ? "rgb(119,17,37)" : undefined }}
+ onClick={() => this.handlePathClick(doc, index)}>{doc.title}
+ </div>
+ </div>);
+ return (<div className="pathbar">
+ <div className="scrollmenu">
+ {items}
+ </div>
+ {!this._parents.length ? (null) :
+ <div className="back" >
+ <FontAwesomeIcon onClick={this.back} icon={"chevron-left"} color="white" size={"2x"} />
+ </div>}
+ <div className="hidePath" />
+ </div>);
}
- shiftRight = (e: React.MouseEvent) => {
- DocServer.Mobile.dispatchOverlayPositionUpdate({
- dx: 10
- });
- e.preventDefault();
- e.stopPropagation();
+ // Handles when user clicks on a document in the pathbar
+ @action
+ handlePathClick = (doc: Doc, index: number) => {
+ if (doc === this._library) {
+ this._child = null;
+ this.switchCurrentView(doc);
+ this._parents.length = index;
+ } else if (doc === this._homeDoc) {
+ this.returnHome();
+ } else {
+ this._child = doc;
+ this.switchCurrentView(doc);
+ this._parents.length = index;
+ }
}
- panelHeight = () => window.innerHeight;
- panelWidth = () => window.innerWidth;
- renderInkingContent = () => {
- console.log("rendering inking content");
- // TODO: support panning and zooming
- // TODO: handle moving of ink strokes
- if (this.mainContainer) {
+ // Renders the contents of the menu and sidebar
+ @computed get renderDefaultContent() {
+ if (this._homeMenu) {
return (
- <div className="mobileInterface">
- <div className="mobileInterface-inkInterfaceButtons">
- <div className="navButtons">
- <button className="mobileInterface-button cancel" onClick={this.onBack} title="Cancel drawing">BACK</button>
- </div>
- <div className="inkSettingButtons">
- <button className="mobileInterface-button cancel" onClick={this.onBack} title="Cancel drawing"><FontAwesomeIcon icon="long-arrow-alt-left" /></button>
- </div>
- <div className="navButtons">
- <button className="mobileInterface-button" onClick={this.shiftLeft} title="Shift left">left</button>
- <button className="mobileInterface-button" onClick={this.shiftRight} title="Shift right">right</button>
+ <div>
+ <div className="navbar">
+ <FontAwesomeIcon className="home" icon="home" onClick={this.returnHome} />
+ <div className="header" id="header">{this._homeDoc.title}</div>
+ <div className="cover" id="cover" onClick={e => e.stopPropagation()}></div>
+ <div className="toggle-btn" id="menuButton" onClick={this.toggleSidebar}>
+ <span></span>
+ <span></span>
+ <span></span>
</div>
</div>
- <CollectionView
- Document={this.mainContainer}
- DataDoc={undefined}
- LibraryPath={emptyPath}
- filterAddDocument={returnTrue}
- fieldKey={""}
- dropAction={"alias"}
- bringToFront={emptyFunction}
- addDocTab={returnFalse}
- pinToPres={emptyFunction}
- PanelWidth={this.panelWidth}
- PanelHeight={this.panelHeight}
- NativeHeight={returnZero}
- NativeWidth={returnZero}
- focus={emptyFunction}
- isSelected={returnFalse}
- select={emptyFunction}
- active={returnFalse}
- ContentScaling={returnOne}
- whenActiveChanged={returnFalse}
- ScreenToLocalTransform={Transform.Identity}
- renderDepth={0}
- docFilters={returnEmptyFilter}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined}
- rootSelected={returnTrue}>
- </CollectionView>
+ {this.renderPathbar()}
</div>
);
}
+ // stores workspace documents as 'workspaces' variable
+ let workspaces = Cast(Doc.UserDoc().myWorkspaces, Doc) as Doc;
+ if (this._child) {
+ workspaces = this._child;
+ }
+ // returns a list of navbar buttons as 'buttons'
+ const buttons = DocListCast(workspaces.data).map((doc: Doc, index: any) => {
+ if (doc.type !== "ink") {
+ return (
+ <div
+ className="item"
+ key={index}>
+ <div className="item-title" onClick={() => this.handleClick(doc)}> {doc.title} </div>
+ <div className="item-type" onClick={() => this.handleClick(doc)}>{doc.type}</div>
+ <FontAwesomeIcon onClick={() => this.handleClick(doc)} className="right" icon="angle-right" size="lg" style={{ display: `${doc.type === "collection" ? "block" : "none"}` }} />
+ <FontAwesomeIcon className="open" onClick={() => this.openFromSidebar(doc)} icon="external-link-alt" size="lg" />
+ </div>
+ );
+ }
+ });
+
+ return (
+ <div>
+ <div className="navbar">
+ <FontAwesomeIcon className="home" icon="home" onClick={this.returnHome} />
+ <div className="header" id="header">{this._sidebarActive ? "library" : this._activeDoc.title}</div>
+ <div className={`toggle-btn ${this._sidebarActive ? "active" : ""}`} onClick={this.toggleSidebar}>
+ <span></span>
+ <span></span>
+ <span></span>
+ </div>
+ <div className={`background ${this._sidebarActive ? "active" : ""}`} onClick={this.toggleSidebar}></div>
+ </div>
+ {this.renderPathbar()}
+ <div className={`sidebar ${this._sidebarActive ? "active" : ""}`}>
+ <div className="sidebarButtons">
+ {this._child ?
+ <>
+ {buttons}
+ <div
+ className="item" key="home"
+ onClick={this.returnMain}
+ style={{ opacity: 0.7 }}>
+ <FontAwesomeIcon className="right" icon="angle-double-left" size="lg" />
+ <div className="item-type">Return to workspaces</div>
+ </div>
+ </> :
+ <>
+ {buttons}
+ <div
+ className="item"
+ style={{ opacity: 0.7 }}
+ onClick={() => this.createNewWorkspace()}>
+ <FontAwesomeIcon className="right" icon="plus" size="lg" />
+ <div className="item-type">Create New Workspace</div>
+ </div>
+ </>
+ }
+ </div>
+ </div>
+ <div className={`blanket ${this._sidebarActive ? "active" : ""}`} onClick={this.toggleSidebar}>
+ </div>
+ </div>
+ );
}
- upload = async (e: React.MouseEvent) => {
- if (this.mainContainer) {
- const data = Cast(this.mainContainer.data, listSpec(Doc));
- if (data) {
- const collectionDoc = await data[1]; // this should be the collection doc since the positions should be locked
- const children = DocListCast(collectionDoc.data);
- const uploadDoc = children.length === 1 ? children[0] : Docs.Create.StackingDocument(children, {
- title: "Mobile Upload Collection", backgroundColor: "white", lockedPosition: true, _width: 300, _height: 300
- });
- if (uploadDoc) {
- DocServer.Mobile.dispatchMobileDocumentUpload({
- docId: uploadDoc[Id],
- });
- }
- }
- }
- e.stopPropagation();
- e.preventDefault();
+ /**
+ * Handles the 'Create New Workspace' button in the menu (taken from MainView.tsx)
+ */
+ @action
+ createNewWorkspace = async (id?: string) => {
+ const workspaces = Cast(Doc.UserDoc().myWorkspaces, Doc) as Doc;
+ const workspaceCount = DocListCast(workspaces.data).length + 1;
+ const freeformOptions: DocumentOptions = {
+ x: 0,
+ y: 400,
+ title: "Collection " + workspaceCount,
+ };
+ const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
+ const workspaceDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600, path: [Doc.UserDoc().myCatalog as Doc] }], { title: `Workspace ${workspaceCount}` }, id, "row");
+
+ const toggleTheme = ScriptField.MakeScript(`self.darkScheme = !self.darkScheme`);
+ const toggleComic = ScriptField.MakeScript(`toggleComicMode()`);
+ const cloneWorkspace = ScriptField.MakeScript(`cloneWorkspace()`);
+ workspaceDoc.contextMenuScripts = new List<ScriptField>([toggleTheme!, toggleComic!, cloneWorkspace!]);
+ workspaceDoc.contextMenuLabels = new List<string>(["Toggle Theme Colors", "Toggle Comic Mode", "New Workspace Layout"]);
+
+ Doc.AddDocToList(workspaces, "data", workspaceDoc);
}
- addWebToCollection = async () => {
- let url = "https://en.wikipedia.org/wiki/Hedgehog";
- if (this.mainContainer) {
- const data = Cast(this.mainContainer.data, listSpec(Doc));
- if (data) {
- const webDoc = await data[0];
- const urlField: FieldResult<WebField> = Cast(webDoc.data, WebField);
- url = urlField ? urlField.url.toString() : "https://en.wikipedia.org/wiki/Hedgehog";
+ // Button for switching between pen and ink mode
+ @action
+ onSwitchInking = () => {
+ const button = document.getElementById("inkButton") as HTMLElement;
+ button.style.backgroundColor = this._ink ? "white" : "black";
+ button.style.color = this._ink ? "black" : "white";
- }
- }
- Docs.Create.WebDocument(url, { _width: 300, _height: 300, title: "Mobile Upload Web Doc" });
- }
-
- clearUpload = async () => {
- if (this.mainContainer) {
- const data = Cast(this.mainContainer.data, listSpec(Doc));
- if (data) {
- const collectionDoc = await data[1];
- const children = DocListCast(collectionDoc.data);
- children.forEach(doc => {
- });
- // collectionDoc[data] = new List<Doc>();
- }
+ if (!this._ink) {
+ Doc.SetSelectedTool(InkTool.Pen);
+ this._ink = true;
+ } else {
+ Doc.SetSelectedTool(InkTool.None);
+ this._ink = false;
}
}
- renderUploadContent() {
- if (this.mainContainer) {
+ // The static ink menu that appears at the top
+ @computed get inkMenu() {
+ return this._activeDoc._viewType !== CollectionViewType.Docking || !this._ink ? (null) :
+ <div className="colorSelector">
+ <InkOptionsMenu />
+ </div>;
+ }
+
+ // DocButton that uses UndoManager and handles the opacity change if CanUndo is true
+ @computed get undo() {
+ if (this.mainContainer && this._activeDoc.type === "collection" && this._activeDoc !== this._homeDoc &&
+ this._activeDoc !== Doc.UserDoc().rightSidebarCollection && this._activeDoc.title !== "WORKSPACES") {
return (
- <div className="mobileInterface" onDragOver={this.onDragOver}>
- <div className="mobileInterface-inkInterfaceButtons">
- <button className="mobileInterface-button cancel" onClick={this.onBack} title="Back">BACK</button>
- {/* <button className="mobileInterface-button" onClick={this.clearUpload} title="Clear Upload">CLEAR</button> */}
- {/* <button className="mobileInterface-button" onClick={this.addWeb} title="Add Web Doc to Upload Collection"></button> */}
- <button className="mobileInterface-button" onClick={this.upload} title="Upload">UPLOAD</button>
- </div>
- <DocumentView
- Document={this.mainContainer}
- DataDoc={undefined}
- LibraryPath={emptyPath}
- addDocument={returnFalse}
- addDocTab={returnFalse}
- pinToPres={emptyFunction}
- rootSelected={returnFalse}
- removeDocument={undefined}
- onClick={undefined}
- ScreenToLocalTransform={Transform.Identity}
- ContentScaling={returnOne}
- NativeHeight={returnZero}
- NativeWidth={returnZero}
- PanelWidth={() => window.screen.width}
- PanelHeight={() => window.screen.height}
- renderDepth={0}
- focus={emptyFunction}
- backgroundColor={returnEmptyString}
- parentActive={returnTrue}
- whenActiveChanged={emptyFunction}
- bringToFront={emptyFunction}
- docFilters={returnEmptyFilter}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined} />
+ <div className="docButton"
+ style={{ backgroundColor: "black", color: "white", fontSize: "60", opacity: UndoManager.CanUndo() ? "1" : "0.4", }}
+ id="undoButton"
+ title="undo"
+ onClick={(e: React.MouseEvent) => {
+ UndoManager.Undo();
+ e.stopPropagation();
+ }}>
+ <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="undo-alt" />
+ </div>);
+ } else return (null);
+ }
+
+ // DocButton that uses UndoManager and handles the opacity change if CanRedo is true
+ @computed get redo() {
+ if (this.mainContainer && this._activeDoc.type === "collection" && this._activeDoc !== this._homeDoc &&
+ this._activeDoc !== Doc.UserDoc().rightSidebarCollection && this._activeDoc.title !== "WORKSPACES") {
+ return (
+ <div className="docButton"
+ style={{ backgroundColor: "black", color: "white", fontSize: "60", opacity: UndoManager.CanRedo() ? "1" : "0.4", }}
+ id="undoButton"
+ title="redo"
+ onClick={(e: React.MouseEvent) => {
+ UndoManager.Redo();
+ e.stopPropagation();
+ }}>
+ <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="redo-alt" />
+ </div>);
+ } else return (null);
+ }
+
+ // DocButton for switching into ink mode
+ @computed get drawInk() {
+ return !this.mainContainer || this._activeDoc._viewType !== CollectionViewType.Docking ? (null) :
+ <div className="docButton"
+ id="inkButton"
+ title={Doc.isDocPinned(this._activeDoc) ? "Pen on" : "Pen off"}
+ onClick={this.onSwitchInking}>
+ <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="pen-nib" />
+ </div>;
+ }
+
+ // DocButton: Button that appears on the bottom of the screen to initiate image upload
+ @computed get uploadImageButton() {
+ if (this._activeDoc.type === DocumentType.COL && this._activeDoc !== this._homeDoc && this._activeDoc._viewType !== CollectionViewType.Docking && this._activeDoc.title !== "WORKSPACES") {
+ return <div className="docButton"
+ id="imageButton"
+ title={Doc.isDocPinned(this._activeDoc) ? "Pen on" : "Pen off"}
+ onClick={this.toggleUpload}>
+ <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="upload" />
+ </div>;
+ } else return (null);
+ }
+
+ // DocButton to download images on the mobile
+ @computed get downloadDocument() {
+ if (this._activeDoc.type === "image" || this._activeDoc.type === "pdf" || this._activeDoc.type === "video") {
+ return <div className="docButton"
+ title={"Download Image"}
+ style={{ backgroundColor: "white", color: "black" }}
+ onClick={e => window.open(this._activeDoc["data-path"]?.toString())}> {/* daa-path holds the url */}
+ <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="download" />
+ </div>;
+ } else return (null);
+ }
+
+ // DocButton for pinning images to presentation
+ @computed get pinToPresentation() {
+ // Only making button available if it is an image
+ if (!(this._activeDoc.type === "collection" || this._activeDoc.type === "presentation")) {
+ const isPinned = this._activeDoc && Doc.isDocPinned(this._activeDoc);
+ return <div className="docButton"
+ title={Doc.isDocPinned(this._activeDoc) ? "Unpin from presentation" : "Pin to presentation"}
+ style={{ backgroundColor: isPinned ? "black" : "white", color: isPinned ? "white" : "black" }}
+ onClick={e => DockedFrameRenderer.PinDoc(this._activeDoc, isPinned)}>
+ <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin" />
+ </div>;
+ } else return (null);
+ }
+
+ // Buttons for switching the menu between large and small icons
+ @computed get switchMenuView() {
+ return this._activeDoc.title !== this._homeDoc.title ? (null) :
+ <div className="homeSwitch">
+ <div className={`list ${!this._menuListView ? "active" : ""}`} onClick={this.changeToIconView}>
+ <FontAwesomeIcon size="sm" icon="th-large" />
</div>
- );
+ <div className={`list ${this._menuListView ? "active" : ""}`} onClick={this.changeToListView}>
+ <FontAwesomeIcon size="sm" icon="bars" />
+ </div>
+ </div>;
+ }
+
+ // Logic for switching the menu into the icons
+ @action
+ changeToIconView = () => {
+ if (this._homeDoc._viewType = "stacking") {
+ this._menuListView = false;
+ this._homeDoc._viewType = "masonry";
+ this._homeDoc.columnWidth = 300;
+ this._homeDoc._columnWidth = 300;
+ const menuButtons = DocListCast(this._homeDoc.data);
+ menuButtons.map(doc => {
+ const buttonData = DocListCast(doc.data);
+ buttonData[1]._nativeWidth = 0.1;
+ buttonData[1]._width = 0.1;
+ buttonData[1]._dimMagnitude = 0;
+ buttonData[1]._opacity = 0;
+ doc._nativeWidth = 400;
+ });
}
}
+ // Logic for switching the menu into the stacking view
+ @action
+ changeToListView = () => {
+ if (this._homeDoc._viewType = "masonry") {
+ this._homeDoc._viewType = "stacking";
+ this._menuListView = true;
+ const menuButtons = DocListCast(this._homeDoc.data);
+ menuButtons.map(doc => {
+ const buttonData = DocListCast(doc.data);
+ buttonData[1]._nativeWidth = 450;
+ buttonData[1]._dimMagnitude = 2;
+ buttonData[1]._opacity = 1;
+ doc._nativeWidth = 900;
+ });
+ }
+ }
+
+ // For setting up the presentation document for the home menu
+ @action
+ setupDefaultPresentation = () => {
+ const presentation = Cast(Doc.UserDoc().activePresentation, Doc) as Doc;
+
+ if (presentation) {
+ this.switchCurrentView(presentation);
+ this._homeMenu = false;
+ }
+ }
+
+ // For toggling image upload pop up
+ @action
+ toggleUpload = () => this._imageUploadActive = !this._imageUploadActive
+
+ // For toggling audio record and dictate pop up
+ @action
+ toggleAudio = () => this._audioUploadActive = !this._audioUploadActive
+
+ // Button for toggling the upload pop up in a collection
+ @action
+ toggleUploadInCollection = () => {
+ const button = document.getElementById("imageButton") as HTMLElement;
+ button.style.backgroundColor = this._imageUploadActive ? "white" : "black";
+ button.style.color = this._imageUploadActive ? "black" : "white";
+
+ this._imageUploadActive = !this._imageUploadActive;
+ }
+
+ // For closing the image upload pop up
+ @action
+ closeUpload = () => {
+ this._imageUploadActive = false;
+ }
+
+ // Returns the image upload pop up
+ @computed get uploadImage() {
+ const doc = !this._homeMenu ? this._activeDoc : Cast(Doc.UserDoc().rightSidebarCollection, Doc) as Doc;
+ return <Uploader Document={doc} />;
+ }
+
+ // Radial menu can only be used if it is a colleciton and it is not a homeDoc
+ // (and cannot be used on Workspace to avoid pin to presentation opening on right)
+ @computed get displayRadialMenu() {
+ return this._activeDoc.type === "collection" && this._activeDoc !== this._homeDoc &&
+ this._activeDoc._viewType !== CollectionViewType.Docking ? <RadialMenu /> : (null);
+ }
+
onDragOver = (e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
}
+ /**
+ * MENU BUTTON
+ * Switch view from mobile menu to access the mobile uploads
+ * Global function name: openMobileUploads()
+ */
+ @action
+ switchToMobileUploads = () => {
+ const mobileUpload = Cast(Doc.UserDoc().rightSidebarCollection, Doc) as Doc;
+ this.switchCurrentView(mobileUpload);
+ this._homeMenu = false;
+ }
+
render() {
- // const content = this.currentView === "main" ? this.mainContent :
- // this.currentView === "ink" ? this.inkContent :
- // this.currentView === "upload" ? this.uploadContent : <></>;
return (
<div className="mobileInterface-container" onDragOver={this.onDragOver}>
- {/* <DocumentDecorations />
- <GestureOverlay>
- {this.renderView ? this.renderView() : this.renderDefaultContent()}
- </GestureOverlay> */}
-
- {/* <DictationOverlay />
- <SharingManager />
- <GoogleAuthenticationManager /> */}
- <DocumentDecorations />
+ <SettingsManager />
+ <div className={`image-upload ${this._imageUploadActive ? "active" : ""}`}>
+ {this.uploadImage}
+ </div>
+ <div className={`audio-upload ${this._audioUploadActive ? "active" : ""}`}>
+ <AudioUpload />
+ </div>
+ {this.switchMenuView}
+ {this.inkMenu}
<GestureOverlay>
- {this.renderView ? this.renderView() : this.renderDefaultContent()}
+ <div style={{ display: "none" }}><RichTextMenu key="rich" /></div>
+ <div className="docButtonContainer">
+ {this.pinToPresentation}
+ {this.downloadDocument}
+ {this.undo}
+ {this.redo}
+ {this.drawInk}
+ {this.uploadImageButton}
+ </div>
+ {this.displayWorkspaces}
+ {this.renderDefaultContent}
</GestureOverlay>
- <PreviewCursor />
- {/* <ContextMenu /> */}
- <RadialMenu />
- <RichTextMenu />
- {/* <PDFMenu />
- <MarqueeOptionsMenu />
- <OverlayView /> */}
+ {this.displayRadialMenu}
</div>
);
}
}
-Scripting.addGlobal(function switchMobileView(doc: (userDoc: Doc) => Doc, renderView?: () => JSX.Element, onSwitch?: () => void) { return MobileInterface.Instance.switchCurrentView(doc, renderView, onSwitch); });
-Scripting.addGlobal(function onSwitchMobileInking() { return MobileInterface.Instance.onSwitchInking(); });
-Scripting.addGlobal(function renderMobileInking() { return MobileInterface.Instance.renderInkingContent(); });
-Scripting.addGlobal(function onSwitchMobileUpload() { return MobileInterface.Instance.onSwitchUpload(); });
-Scripting.addGlobal(function renderMobileUpload() { return MobileInterface.Instance.renderUploadContent(); });
-Scripting.addGlobal(function addWebToMobileUpload() { return MobileInterface.Instance.addWebToCollection(); });
+//Global functions for mobile menu
+Scripting.addGlobal(function switchToMobileLibrary() { return MobileInterface.Instance.switchToLibrary(); },
+ "opens the library to navigate through workspaces on Dash Mobile");
+Scripting.addGlobal(function openMobileUploads() { return MobileInterface.Instance.toggleUpload(); },
+ "opens the upload files menu for Dash Mobile");
+Scripting.addGlobal(function switchToMobileUploadCollection() { return MobileInterface.Instance.switchToMobileUploads(); },
+ "opens the mobile uploads collection on Dash Mobile");
+Scripting.addGlobal(function openMobileAudio() { return MobileInterface.Instance.toggleAudio(); },
+ "opens the record and dictate menu on Dash Mobile");
+Scripting.addGlobal(function switchToMobilePresentation() { return MobileInterface.Instance.setupDefaultPresentation(); },
+ "opens the presentation on Dash Mobile");
+Scripting.addGlobal(function openMobileSettings() { return SettingsManager.Instance.open(); },
+ "opens settings on Dash Mobile");
+
+// Other global functions for mobile
+Scripting.addGlobal(function switchMobileView(doc: Doc, renderView?: () => JSX.Element, onSwitch?: () => void) { return MobileInterface.Instance.switchCurrentView(doc, renderView, onSwitch); },
+ "changes the active document displayed on the Dash Mobile", "(doc: any)");
diff --git a/src/mobile/MobileMain.tsx b/src/mobile/MobileMain.tsx
new file mode 100644
index 000000000..3d4680d58
--- /dev/null
+++ b/src/mobile/MobileMain.tsx
@@ -0,0 +1,25 @@
+import { MobileInterface } from "./MobileInterface";
+import { Docs } from "../client/documents/Documents";
+import { CurrentUserUtils } from "../client/util/CurrentUserUtils";
+import * as ReactDOM from 'react-dom';
+import * as React from 'react';
+import { DocServer } from "../client/DocServer";
+import { AssignAllExtensions } from "../extensions/General/Extensions";
+
+AssignAllExtensions();
+
+(async () => {
+ const info = await CurrentUserUtils.loadCurrentUser();
+ DocServer.init(window.location.protocol, window.location.hostname, 4321, info.email + " (mobile)");
+ await Docs.Prototypes.initialize();
+ if (info.id !== "__guest__") {
+ // a guest will not have an id registered
+ await CurrentUserUtils.loadUserDocument(info);
+ }
+ document.getElementById('root')!.addEventListener('wheel', event => {
+ if (event.ctrlKey) {
+ event.preventDefault();
+ }
+ }, true);
+ ReactDOM.render(<MobileInterface />, document.getElementById('root'));
+})(); \ No newline at end of file
diff --git a/src/mobile/MobileMenu.scss b/src/mobile/MobileMenu.scss
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/mobile/MobileMenu.scss
diff --git a/src/pen-gestures/ndollar.ts b/src/pen-gestures/ndollar.ts
index 9d42035d1..ecd8df3e7 100644
--- a/src/pen-gestures/ndollar.ts
+++ b/src/pen-gestures/ndollar.ts
@@ -552,7 +552,7 @@ function Distance(p1: any, p2: any) // distance between two points
}
function CalcStartUnitVector(points: any, index: any) // start angle from points[0] to points[index] normalized as a unit vector
{
- const v = new Point(points[index].X - points[0].X, points[index].Y - points[0].Y);
+ const v = new Point(points[index]?.X - points[0]?.X, points[index]?.Y - points[0]?.Y);
const len = Math.sqrt(v.X * v.X + v.Y * v.Y);
return new Point(v.X / len, v.Y / len);
}
diff --git a/src/server/ApiManagers/PDFManager.ts b/src/server/ApiManagers/PDFManager.ts
index e028d628d..2b4212588 100644
--- a/src/server/ApiManagers/PDFManager.ts
+++ b/src/server/ApiManagers/PDFManager.ts
@@ -114,4 +114,4 @@ class NodeCanvasFactory {
canvasAndContext.canvas = null;
canvasAndContext.context = null;
}
-} \ No newline at end of file
+}
diff --git a/src/server/index.ts b/src/server/index.ts
index 590affd06..083173bb5 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -104,6 +104,7 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
const serve: PublicHandler = ({ req, res }) => {
const detector = new mobileDetect(req.headers['user-agent'] || "");
const filename = detector.mobile() !== null ? 'mobile/image.html' : 'index.html';
+ console.log(detector.is("iPhone"));
res.sendFile(path.join(__dirname, '../../deploy/' + filename));
};
diff --git a/webpack.config.js b/webpack.config.js
index bd1d3b5a9..a5fe6ad80 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -43,7 +43,7 @@ module.exports = {
repl: ["./src/debug/Repl.tsx", 'webpack-hot-middleware/client?reload=true'],
test: ["./src/debug/Test.tsx", 'webpack-hot-middleware/client?reload=true'],
inkControls: ["./src/mobile/InkControls.tsx", 'webpack-hot-middleware/client?reload=true'],
- imageUpload: ["./src/mobile/ImageUpload.tsx", 'webpack-hot-middleware/client?reload=true'],
+ mobileInterface: ["./src/mobile/MobileMain.tsx", 'webpack-hot-middleware/client?reload=true'],
},
optimization: {
noEmitOnErrors: true
@@ -66,42 +66,42 @@ module.exports = {
},
module: {
rules: [{
- test: [/\.tsx?$/],
- use: [{
- loader: 'ts-loader',
- options: {
- transpileOnly: true
- }
- }]
- },
- {
- test: /\.scss|css$/,
- use: [{
- loader: "style-loader"
+ test: [/\.tsx?$/],
+ use: [{
+ loader: 'ts-loader',
+ options: {
+ transpileOnly: true
+ }
+ }]
},
{
- loader: "css-loader"
+ test: /\.scss|css$/,
+ use: [{
+ loader: "style-loader"
+ },
+ {
+ loader: "css-loader"
+ },
+ {
+ loader: "sass-loader"
+ }
+ ]
},
{
- loader: "sass-loader"
+ test: /\.(jpg|png|pdf)$/,
+ use: [{
+ loader: 'file-loader'
+ }]
+ },
+ {
+ test: /\.(png|jpg|gif)$/i,
+ use: [{
+ loader: 'url-loader',
+ options: {
+ limit: 8192
+ }
+ }]
}
- ]
- },
- {
- test: /\.(jpg|png|pdf)$/,
- use: [{
- loader: 'file-loader'
- }]
- },
- {
- test: /\.(png|jpg|gif)$/i,
- use: [{
- loader: 'url-loader',
- options: {
- limit: 8192
- }
- }]
- }
]
},
plugins,